/Partitions/` displays the M as `expression`, but `te set -q expression …` errors with "Property 'expression' not found on MPartition. Did you mean: MExpression?"; use `-q MExpression -i "" --save` to update.
-- **`te mv` cannot rename a partition to its parent table's name**: `te mv /Partitions/_m /Partitions/` errors with either "Destination '/' already exists" or "Partition '' not found in table ''"; the destination path `/` resolves to the **table object itself**, not a partition slot inside the table. Passing `-t Partition` does **not** disambiguate. Workaround: rename via the `Name` property; `te set /Partitions/_m -q Name -i --save`.
+- **`te move` cannot rename a partition to its parent table's name**: `te move /Partitions/_m /Partitions/` errors with either "Destination '/' already exists" or "Partition '' not found in table ''"; the destination path `/` resolves to the **table object itself**, not a partition slot inside the table. Passing `-t Partition` does **not** disambiguate. Workaround: rename via the `Name` property; `te set /Partitions/_m -q Name -i --save`.
- **Object paths use `/` as separator**, not `\` or `.`. Quote paths with spaces: `"Sales 2024"/Revenue`, `"_Measures/Total Revenue"`, `"Date/Date Hierarchy/Year"`.
-- **`te ls` cannot list relationships**: both `te ls Relationships` and `te ls --type relationship` error with `Error: No objects match path 'Relationships'` even when the model has relationships (confirmed against a 23-relationship model, local TMDL and live XMLA alike). `relationship` is advertised in `te ls --type`'s help, but unlike every other container (`Tables`, `Measures`, `Roles`, `Perspectives`, `Cultures`, `Hierarchies`) the keyword is not wired to the collection, so it falls through to a literal path match. Tell: recognized-but-empty containers say `No objects match 'X'`; the broken keyword says `No objects match path 'X'` (note the word `path`). Enumerate relationships with DAX instead: `te query -q "EVALUATE INFO.VIEW.RELATIONSHIPS()"` (friendly `from ∞←1 to`, active flag, cross-filter direction), or `INFO.RELATIONSHIPS()` on older compat, or `te save` to TMDL and read `relationships.tmdl`. A single relationship is addressable by name via `te get Relationships/`, but auto-assigned names (`Relationship 2`) make DAX enumeration the practical discovery path.
+- **Relationship names are often auto-assigned**: `te list Relationships` and `te list --type relationship` do enumerate every relationship, but the display name is frequently a system-generated GUID or `Relationship 2` rather than something readable. For a human-friendly view with cross-filter direction and active flag, prefer `te query "EVALUATE INFO.VIEW.RELATIONSHIPS()"` (or `INFO.RELATIONSHIPS()` on older compat). A single relationship is addressable by name via `te get Relationships/` once you know it.
### Object types and creation
- **`-t DataColumn` is not accepted by `te add`**: data columns are declared at table creation via `--columns "Name:Type,..."` (auto-creates with `SourceColumn = Name`). Tune additional properties (`IsKey`, `IsHidden`, `FormatString`, `SummarizeBy`, `SortByColumn`, `Description`) afterwards with `te set`. Only `CalculatedColumn` can be added individually with `-t CalculatedColumn -i ""`.
- **Workflow ordering; relationships before measures**: measures that use `RELATED()` or cross-table `CALCULATE()` are validated at save time. If the relationship doesn't exist yet, the save gate rejects with `DAX0002: Column ''[] doesn't have a relationship to any table available in the current context`. Add relationships before authoring dependent measures. If that order is impossible (scripted batch creation), use `--force` only as a last resort and run `te validate` immediately after to confirm no DAX errors remain before committing.
-- **`te rm` on the last partition fails** with "Cannot remove last partition from table". Always add a replacement partition first, then remove the original.
+- **`te remove` on the last partition fails** with "Cannot remove last partition from table". Always add a replacement partition first, then remove the original.
- **Silent M syntax errors during partition authoring**: a typo in the partition M (unbalanced braces, an unquoted token) doesn't always raise at save time. After creating a data-bound table, sanity-check with `te get /Partitions/` to confirm `sourceType: M` and that the expression matches what was passed in.
### Output shapes
@@ -29,12 +29,12 @@ Sharp edges and non-obvious behavior. Companion to the te-cli skill (SKILL.md).
### Behavior
-- **`te ls` is filesystem-style, not workspace-style**. `te ls Sales` lists Sales' children (columns + measures), not "find Sales". Use `te find` for full-text search.
-- **`--save` is opt-in for editing commands** (when `interactiveEditMode` is the default `stage`). Without it, `te set`, `te add`, `te rm`, `te mv`, `te replace`, `te format`, `te script`, `te macro run` operate in memory only and don't persist. See the Staging model section in SKILL.md for the full picture and the `save` / `revert` alternatives.
+- **`te list` is filesystem-style, not workspace-style**. `te list Sales` lists Sales' children (columns + measures), not "find Sales". Use `te find` for full-text search.
+- **`--save` is opt-in for editing commands** (when `interactiveEditMode` is the default `stage`). Without it, `te set`, `te add`, `te remove`, `te move`, `te replace`, `te format`, `te script`, `te macro run` operate in memory only and don't persist. See the Staging model section in SKILL.md for the full picture and the `save` / `revert` alternatives.
- **`te validate` does not exercise partition M**; it checks structural/DAX validity, not whether `Table.FromRows` literals parse or SQL endpoints respond. A model with broken partitions still passes `te validate` cleanly. Verify partitions explicitly after table creation.
- **`te connect` is session-scoped (per shell PID)**. Each fresh shell (each Claude Code `Bash` tool call spawns one) starts a new session without the active connection. Either set `TE_SESSION=` to share state between shells, or pass `-m ` (and `-s`/`-d` where needed) explicitly to every command.
- **BPA config keys are nested under `bpa.`**. `te config set bpaOnDeploy false` will fail with "Unknown key"; the correct form is `te config set bpa.onDeploy false`. Same for `bpa.onSave`, `bpa.onMutation`, `bpa.rules`, `bpa.builtInRules`, `bpa.disabledBuiltInRuleIds`.
-- **`--output-format` (stdout) vs `--serialization` (on-disk)** are different flags. The first picks how stdout is rendered (text/json/csv/tmsl/tmdl); the second picks the model file format (tmdl/bim/te-folder/pbip/database.json). Passing one when the other was meant gives a confusing error or silent wrong output.
+- **`--output-format` (stdout) vs `--serialization` (on-disk)** are different flags. The first picks how stdout is rendered (text/json/csv/tmsl/tmdl); the second picks the model file format (tmdl/bim/database.json/pbip). Passing one when the other was meant gives a confusing error or silent wrong output. `--serialization tmsl` is accepted as an alias for `bim` (matching how `--output-format` already treats the two synonyms); the canonical spelling is `bim`, matching the `.bim` file extension.
- **`te deploy --create-only` fails if model exists**. Use without `--create-only` to overwrite (the default), or check with a probe call first.
- **`te deploy` confirmation prompt hangs CI** without `--force`. Default answer is `n` (safe), so non-interactive runs need `--force --non-interactive`.
- **BPA gate on deploy/save can mask sloppy commits**. If bypassing with `--skip-bpa`, log it loudly. Prefer `--fix-bpa` or address violations.
@@ -42,4 +42,3 @@ Sharp edges and non-obvious behavior. Companion to the te-cli skill (SKILL.md).
- **Local SSAS / Power BI Desktop are Windows-only**: `te connect --local` and `te connect "localhost:PORT"` won't work on macOS/Linux even though the binary runs there.
- **Preview banner reappears 14 days before the 2026-09-30 cutoff** regardless of `hidePreviewNotice`. Plan for the hard expiry.
- **Secrets on the cmd line leak** into `ps`, shell history, CI logs. Use `--auth env` with env vars, stdin (`-`), or `--auth managed-identity`.
-
diff --git a/skills/te-cli/references/pbir-cli-tandem.md b/skills/te-cli/references/pbir-cli-tandem.md
index 2665fd9..0424468 100644
--- a/skills/te-cli/references/pbir-cli-tandem.md
+++ b/skills/te-cli/references/pbir-cli-tandem.md
@@ -6,7 +6,7 @@ Two rules before every command: model edits in `te` stage in memory and need `--
## 1. Rename a measure (model) and repair report bindings
-The most common refactor. `te mv` renames the object only; it does not rewrite DAX that calls the old name, so `te replace --in expressions` is a mandatory second step.
+The most common refactor. `te move` renames the object only; it does not rewrite DAX that calls the old name, so `te replace --in expressions` is a mandatory second step.
```bash
# te: confirm the object and find DAX that calls it by name
@@ -15,7 +15,7 @@ te find "OldRevenue" --in expressions -m ./Model.SemanticModel # measures, cal
te deps "_Measures/OldRevenue" --downstream -m ./Model.SemanticModel # blast radius
# te: rename the object, then fix every DAX reference to the old name
-te mv "_Measures/OldRevenue" "_Measures/Revenue" --save -m ./Model.SemanticModel
+te move "_Measures/OldRevenue" "_Measures/Revenue" --save -m ./Model.SemanticModel
te replace "OldRevenue" "Revenue" --in expressions --save -m ./Model.SemanticModel
# te: gate before touching the report
@@ -36,9 +36,9 @@ Per-step purpose:
```yaml
te find --in names: confirm the measure exists and get its path before touching anything
-te find --in expressions: discover DAX that calls the old name by value; te mv will NOT fix these
+te find --in expressions: discover DAX that calls the old name by value; te move will NOT fix these
te deps --downstream: list measures/columns downstream to show the full impact
-te mv: rename the TOM object (the name property only)
+te move: rename the TOM object (the name property only)
te replace --in expressions: rewrite every DAX call of the old name across the model; needs --save
te validate --errors-only: confirm no broken DAX references remain
pbir fields find: locate visuals, filters, and CF entries bound to the old reference
@@ -49,12 +49,12 @@ pbir validate --fields: confirm all bindings resolve; zero broken references exp
## 2. Rename a column (model) and repair report bindings
-Same shape as a measure rename, plus column-only metadata to check after `te mv`.
+Same shape as a measure rename, plus column-only metadata to check after `te move`.
```bash
te find "OldColumnName" --in names --paths-only -m ./Model.SemanticModel # check for same name on other tables
te deps "Date/OldColumnName" --downstream -m ./Model.SemanticModel
-te mv "Date/OldColumnName" "Date/NewColumnName" --save -m ./Model.SemanticModel
+te move "Date/OldColumnName" "Date/NewColumnName" --save -m ./Model.SemanticModel
te replace "OldColumnName" "NewColumnName" --in expressions --save -m ./Model.SemanticModel
te validate -m ./Model.SemanticModel --errors-only
@@ -68,7 +68,7 @@ After the rename, verify two column relationships that store the old name as a p
```bash
te get Date/SomeOtherColumn -q sortByColumn -m ./Model.SemanticModel # update with te set if it broke
-te ls "Date/Geography/Levels" -m ./Model.SemanticModel # hierarchy levels take a column property
+te list "Date/Geography/Levels" -m ./Model.SemanticModel # hierarchy levels take a column property
```
## 3. Rename a table (model) and repair all report bindings
@@ -79,7 +79,7 @@ te ls "Date/Geography/Levels" -m ./Model.SemanticModel # hierar
te find "FACT_Sales" --in names -m ./Model.SemanticModel
te find "FACT_Sales" --in expressions -m ./Model.SemanticModel
te replace "FACT_Sales" "Sales" --in expressions --save -m ./Model.SemanticModel
-te mv FACT_Sales Sales --save -m ./Model.SemanticModel
+te move FACT_Sales Sales --save -m ./Model.SemanticModel
te validate -m ./Model.SemanticModel --errors-only
# pbir: list fields, then replace each FACT_Sales.* binding individually
@@ -93,16 +93,16 @@ pbir validate "Report.Report" --fields
```yaml
te replace --in expressions: rewrites DAX text only; apostrophe-quoted refs need the quotes in the find term, e.g. te replace "'FACT_Sales'" "'Sales'" --in expressions --save
te replace substring risk: if the old name is a substring of another identifier, add --case-sensitive or --regex with anchors and review the preview
-relationship endpoints: te mv renames the table object; confirm relationship integrity with te validate
+relationship endpoints: te move renames the table object; confirm relationship integrity with te validate
```
## 4. Move a measure to a different table and update bindings
-`te mv` across tables is the only way to change an object's table ownership. Only fully table-qualified DAX (`SourceTable[Measure]`) breaks; unqualified `[Measure]` keeps resolving model-wide.
+`te move` across tables is the only way to change an object's table ownership. Only fully table-qualified DAX (`SourceTable[Measure]`) breaks; unqualified `[Measure]` keeps resolving model-wide.
```bash
te deps "SourceTable/MeasureName" --downstream -m ./Model.SemanticModel
-te mv "SourceTable/MeasureName" "TargetTable/MeasureName" --save -m ./Model.SemanticModel
+te move "SourceTable/MeasureName" "TargetTable/MeasureName" --save -m ./Model.SemanticModel
te find "SourceTable" --in expressions --paths-only -m ./Model.SemanticModel # decide if te replace is needed
te validate -m ./Model.SemanticModel --errors-only
@@ -111,7 +111,7 @@ pbir fields replace "Report.Report" --from "SourceTable.MeasureName" --to "Targe
pbir validate "Report.Report" --fields
```
-Display folder does not move with `te mv`; reset it on the moved measure if it should match the new table's folder structure (`te set "TargetTable/MeasureName" -q displayFolder -i "" --save`).
+Display folder does not move with `te move`; reset it on the moved measure if it should match the new table's folder structure (`te set "TargetTable/MeasureName" -q displayFolder -i "" --save`).
## 5. Scaffold a model, then create a thin report against it
@@ -155,7 +155,7 @@ pbir report rebind "Sales.Report" --local "../Sales.SemanticModel"
## 6. Add a measure to a live model, then surface it in a bound report
```bash
-te ls Measures -m ./Model.SemanticModel # check naming conventions, avoid duplicates
+te list Measures -m ./Model.SemanticModel # check naming conventions, avoid duplicates
te add "_Measures/Revenue YoY" -t Measure -i "DIVIDE([Revenue], CALCULATE([Revenue], SAMEPERIODLASTYEAR('Date'[Date]))) - 1" --save -m ./Model.SemanticModel
te set "_Measures/Revenue YoY" -q formatString -i "0.0%" --save -m ./Model.SemanticModel
te set "_Measures/Revenue YoY" -q displayFolder -i "Revenue" --save -m ./Model.SemanticModel
@@ -185,15 +185,15 @@ pbir validate "Report.Report"
pbir visuals bind "Report.Report/Page.Page/Visual.Visual" -r "Category:Sales.OldRegionCode"
# te: delete only after the report is clean
-te rm Sales/OldRegionCode --dry-run -m ./Model.SemanticModel
-te rm Sales/OldRegionCode --if-exists --save -m ./Model.SemanticModel
+te remove Sales/OldRegionCode --dry-run -m ./Model.SemanticModel
+te remove Sales/OldRegionCode --if-exists --save -m ./Model.SemanticModel
te validate -m ./Model.SemanticModel --errors-only
te deploy ./Model.SemanticModel -s "MyWorkspace" -d "Sales Model" --force --non-interactive
```
```yaml
removal granularity: pbir visuals bind -r removes one role binding on one visual; prefer it over a report- or page-wide pbir fields clear, which strips bindings broadly and can leave visuals with empty roles
-sort-by dependency: te rm fails if the column is another column's sortByColumn target; clear that first with te set OtherColumn -q sortByColumn -i "" --save
+sort-by dependency: te remove fails if the column is another column's sortByColumn target; clear that first with te set OtherColumn -q sortByColumn -i "" --save
```
## 8. Split a thick PBIP, edit the model, keep the report in sync
@@ -230,9 +230,9 @@ pbir publish "Sales.Report" "MyWorkspace/Sales" -f # position
```yaml
te owns:
- - object identity (te mv / te set -q name) and DAX expression repair (te replace --in expressions)
+ - object identity (te move / te set -q name) and DAX expression repair (te replace --in expressions)
- dependency analysis (te deps), validation (te validate), BPA (te bpa run), deploy (te deploy)
- - te mv renames the object only; it does NOT rewrite DAX that calls the old name
+ - te move renames the object only; it does NOT rewrite DAX that calls the old name
- te replace previews by default; pass --save to persist; it is literal text find-replace (use --regex / --case-sensitive for substring or quoted-name cases)
- te find --in expressions covers measure DAX, calc columns, KPI expressions, partition M, role filters, and calc-group selection; it does NOT see report JSON
diff --git a/skills/te-cli/references/semantic-modeling-practices.md b/skills/te-cli/references/semantic-modeling-practices.md
index 3bed35c..02e2e25 100644
--- a/skills/te-cli/references/semantic-modeling-practices.md
+++ b/skills/te-cli/references/semantic-modeling-practices.md
@@ -14,12 +14,12 @@ Build a star: one fact table per business process at a single grain, surrounded
| Practice | Why | `te` command |
|---|---|---|
-| Model a star, not a flat table | flat models measured up to ~2.65x larger in RAM and up to 2x slower on complex queries; where flat wins it is marginal | inspect topology first: `te ls`, `te query -q "EVALUATE INFO.VIEW.RELATIONSHIPS()"` (relationships; `te ls` cannot list them), `te deps "Sales/Revenue"` |
+| Model a star, not a flat table | flat models measured up to ~2.65x larger in RAM and up to 2x slower on complex queries; where flat wins it is marginal | inspect topology first: `te list` (tables), `te list Relationships` (or DAX `EVALUATE INFO.VIEW.RELATIONSHIPS()` for the friendly cross-filter view), `te deps "Sales/Revenue"` |
| One consistent grain per fact table; never join fact-to-fact (header/detail) directly | mixing grains gives wrong aggregations; fact-to-fact becomes a limited relationship (SQLBI's 1.4B-row test: ~15x CPU, 17 minutes vs seconds for distinct count) | add a shared dimension and relate both facts to it rather than to each other |
| Flatten snowflake sub-dimensions into one dimension per business entity | extra hops cost CPU on every filter pass and a hierarchy cannot span tables; run-length encoding makes the duplication cheap. Keep a sub-dimension separate only when it is large and reused across parents | do the join in Power Query upstream, then relate the single dimension |
| Keep degenerate dimensions (order/invoice number) as hidden fact columns | a one-column table for a high-cardinality attribute adds size and clutter for no gain | `te set Sales/OrderNumber -q isHidden -i true --save` |
-| Host model measures on a dedicated hidden measure table; prefix to sort it to the top | separates calculations from data and keeps the field list clean (organizational, not an engine requirement; below ~20 measures, display folders inside the source table are equally valid) | `te add _Measures -t Table --columns "_Measures:String" --partition-expression 'let Source = #table({"_Measures"}, {{""}}) in Source' --save`, then `te set _Measures -q isHidden -i true --save`, then `te mv Sales/Revenue "_Measures/Revenue" --save` |
-| Use plain business names (Customer, Sales); drop DIM_/FACT_ prefixes | a table's role comes from cardinality, not its name; prefixes degrade Q&A and Copilot interpretation (organizational, bends to warehouse naming constraints) | `te mv FACT_Sales Sales --save` |
+| Host model measures on a dedicated hidden measure table; prefix to sort it to the top | separates calculations from data and keeps the field list clean (organizational, not an engine requirement; below ~20 measures, display folders inside the source table are equally valid) | `te add _Measures -t Table --columns "_Measures:String" --partition-expression 'let Source = #table({"_Measures"}, {{""}}) in Source' --save`, then `te set _Measures -q isHidden -i true --save`, then `te move Sales/Revenue "_Measures/Revenue" --save` |
+| Use plain business names (Customer, Sales); drop DIM_/FACT_ prefixes | a table's role comes from cardinality, not its name; prefixes degrade Q&A and Copilot interpretation (organizational, bends to warehouse naming constraints) | `te move FACT_Sales Sales --save` |
Nuance to preserve: SQLBI shows a flat model can beat star for some simple low-cardinality groupings, so "star" is the default rather than a universal absolute; when flat wins it is marginal, when it loses it is far worse.
@@ -32,7 +32,7 @@ Default every relationship to one-to-many cardinality and single cross-filter di
| Practice | Why | `te` command |
|---|---|---|
| Add the dimension-to-fact relationship before authoring measures that cross it | measures using `RELATED()` or cross-table `CALCULATE()` validate at save time and the gate rejects with `DAX0002` if the relationship is missing | `te add "Sales[ProductKey]->Product[ProductKey]" --save` |
-| Keep single cross-filter direction; never set model-level bidirectional just to sync slicers | bidirectional on a model with 3+ related tables creates two propagation paths and subtly wrong numbers; scope the rare genuine need to one measure with `CROSSFILTER(..., BOTH)`. Since 2019 a visual-level "is not blank" measure filter replaces the slicer-sync use case | list relationships with `te query -q "EVALUATE INFO.VIEW.RELATIONSHIPS()"` (`te ls` cannot enumerate them; see gotchas), read one with `te get Relationships/` (the `->` shorthand is for `te add` only); change `crossFilteringBehavior` only after confirming the property name with `te set -q`, or use `te script` |
+| Keep single cross-filter direction; never set model-level bidirectional just to sync slicers | bidirectional on a model with 3+ related tables creates two propagation paths and subtly wrong numbers; scope the rare genuine need to one measure with `CROSSFILTER(..., BOTH)`. Since 2019 a visual-level "is not blank" measure filter replaces the slicer-sync use case | list relationships with `te list Relationships` (or DAX `EVALUATE INFO.VIEW.RELATIONSHIPS()` for the friendly cross-filter view), read one with `te get Relationships/` (the `->` shorthand is for `te add` only); change `crossFilteringBehavior` only after confirming the property name with `te set -q`, or use `te script` |
| Treat many-to-many cardinality as a last resort; bridge dimension-to-dimension via two one-to-many relationships (exactly one leg bidirectional) | one-to-many gives a regular relationship with a blank row for RI violations; many-to-many cardinality gives a limited relationship with no join index, multiple passes, and silent drops of unmatched values | model the bridge with `te add` relationships rather than a direct many-to-many |
| When many-to-many is genuinely required, document that totals are non-additive | with shared membership the total counts each item once and will not equal the sum of visible rows; this is correct by design, the risk is silent misreading | add a `te set -q description` note on affected measures |
| Role-playing dimensions: duplicate the physical dimension with an active relationship per role; use one shared table with inactive relationships + `USERELATIONSHIP` only when simultaneous multi-role filtering is not needed | active relationships give correct drag-and-drop results with no DAX; inactive ones force a `USERELATIONSHIP` wrapper in every measure and break Q&A. Never use `USERELATIONSHIP` in a calculated column (use `LOOKUPVALUE`) | load the dimension twice in Power Query (Order Date, Ship Date), each with its own active relationship |
@@ -52,7 +52,7 @@ Column cardinality (distinct value count), not row count, is the primary driver
| Prefer narrow integer surrogate keys for relationship columns | the win is a smaller dictionary for the same distinct count, NOT an encoding switch (VertiPaq always hash-encodes relationship columns); SQLBI measured a relationship dropping from 4 MB to under 50 KB | `te set Sales/CustomerKey -q dataType -i int64 --save` |
| Use a Date data type (not integer YYYYMMDD) for the date key | SQLBI's 2B-row test found storage and scan essentially identical, so usability decides: Date enables native arithmetic and classic time intelligence | `te set Date/Date -q dataType -i dateTime --save` |
| Use Fixed Decimal (Currency) for monetary values; avoid Double/Single for aggregated columns | VertiPaq scans segments in parallel and floating-point addition is non-associative, so Double sums can vary between runs; Fixed Decimal is a deterministic scaled integer | `te set Sales/Amount -q dataType -i decimal --save` |
-| Remove unused columns before deployment, not after | every imported column costs dictionary, data-segment, and attribute-hierarchy memory; removing one 50M-value identity column cut a model from 1.3 GB to 490 MB | `te deps Sales/SomeCol --downstream` to check, then `te rm Sales/SomeCol --dry-run` and `te rm Sales/SomeCol --if-exists --save` |
+| Remove unused columns before deployment, not after | every imported column costs dictionary, data-segment, and attribute-hierarchy memory; removing one 50M-value identity column cut a model from 1.3 GB to 490 MB | `te deps Sales/SomeCol --downstream` to check, then `te remove Sales/SomeCol --dry-run` and `te remove Sales/SomeCol --if-exists --save` |
| Set `isAvailableInMDX` = false on high-cardinality hidden columns (keep it true for Sort By Column targets when MDX/Excel clients exist) | the per-column attribute hierarchy is pure overhead on hidden columns unreachable from MDX (one case: 1.1 GB of hierarchy) | confirm the property name first with `te set -q`, then `te set Sales/Key -q isAvailableInMDX -i false --save` (or `te script`) |
| Reduce numeric precision to what analysis needs when the business agrees | fewer distinct values means smaller dictionaries; rounding loses auditability, so validate with owners and consider keeping a hidden full-precision copy | round in Power Query, then re-profile with `te vertipaq` |
@@ -70,7 +70,7 @@ A model is consumer-ready and Copilot-ready only once measures and columns carry
| Hide surrogate/foreign-key columns and set `summarizeBy` = none on numeric columns that should not aggregate | keys are scaffolding, not attributes; `none` removes the implicit-aggregation sigma so a key cannot be summed into nonsense, and Copilot/Q&A exclude hidden columns | `te set Sales/CustomerKey -q isHidden -i true --save` and `te set Sales/CustomerKey -q summarizeBy -i none --save` |
| Apply Sort By Column to display columns with non-alphabetical order (month, weekday, fiscal period) and hide the helper | without it "Month Name" sorts April, August, December; the sort column must be 1:1 with the display column | `te set Date/MonthName -q sortByColumn -i MonthNumber --save` then `te set Date/MonthNumber -q isHidden -i true --save` |
| Add descriptions to visible tables, columns, and measures; front-load the key guidance in the first 200 characters | descriptions feed Copilot/data-agent DAX (which reads the first 200 chars) and surface as service tooltips for humans | `te set "_Measures/Revenue" -q description -i "Net revenue after returns and discounts" --save` |
-| Use human-readable, unique, business-aligned names; organize measures into display folders; avoid duplicate column names across tables | ambiguous or duplicated names make Copilot/Q&A misroute; folders keep a large field list navigable (purely cosmetic) | `te set "_Measures/Revenue" -q displayFolder -i "Revenue" --save`; qualify clashes as "Customer Name" vs "Store Name" with `te mv` |
+| Use human-readable, unique, business-aligned names; organize measures into display folders; avoid duplicate column names across tables | ambiguous or duplicated names make Copilot/Q&A misroute; folders keep a large field list navigable (purely cosmetic) | `te set "_Measures/Revenue" -q displayFolder -i "Revenue" --save`; qualify clashes as "Customer Name" vs "Store Name" with `te move` |
| Mark `isKey` on dimension primary keys, then hide them | signals the unique grain the engine builds filters from, and keeps the field list clean | `te set Product/ProductKey -q isKey -i true --save` then `te set Product/ProductKey -q isHidden -i true --save` |
| Set data categories on geographic, URL, and image columns | enables map visuals, clickable links, and image rendering, and Copilot reads them for grounding | `te set Geo/City -q dataCategory -i City --save`, `te set Customer/Photo -q dataCategory -i ImageUrl --save` (confirm category values with `te get`) |
@@ -90,7 +90,7 @@ Time intelligence needs one dedicated, shared Date dimension that all facts rela
|---|---|---|
| Build one shared Date dimension; never drive time intelligence off a fact datetime column | classic time-intelligence needs a separate date spine to apply `REMOVEFILTERS` against, and one shared table lets a single slicer drive every fact | relate each fact date to `Date[Date]` with `te add "Sales[OrderDate]->Date[Date]" --save` |
| Mark the Date table (`dataCategory` = Time) and designate the date key | marking designates the date spine so time-intelligence functions (`DATESYTD`, `SAMEPERIODLASTYEAR`) know which column to clear and enumerate against, and it suppresses auto date/time; without it those functions intersect the current filter context and return silent wrong results | `te set Date -q dataCategory -i Time --save` then `te set Date/Date -q isKey -i true --save` |
-| Remove auto date/time tables (`LocalDateTable_*`, `DateTableTemplate_*`) | auto date/time creates a hidden calculated table per date column, inflating size and refresh, cannot be shared, and is invisible to Excel/paginated/XMLA clients | find them with `te ls`, remove with `te rm --save`; disable the toggle at the report/source so they do not regenerate on the next author save |
+| Remove auto date/time tables (`LocalDateTable_*`, `DateTableTemplate_*`) | auto date/time creates a hidden calculated table per date column, inflating size and refresh, cannot be shared, and is invisible to Excel/paginated/XMLA clients | find them with `te list`, remove with `te remove --save`; disable the toggle at the report/source so they do not regenerate on the next author save |
| Keep blank/null dates in the fact rather than inflating the Date table; guard comparisons with `NOT ISBLANK()` | blanks on the many side add a `(Blank)` slicer row and distort totals, and comparison operators treat `BLANK` as a pre-1900 date | author guarded measures with `te add`/`te set -q expression` |
| Standardize one org-wide Date definition (warehouse DimDate, dataflow, or parameterized DAX) | a shared definition prevents fiscal-year, week-numbering, and naming drift across models | import the warehouse DimDate where one exists |
@@ -128,7 +128,7 @@ te set "Time Intelligence/YTD" -q ordinal -i 0 --save
te set "Time Intelligence/YTD" -q formatStringDefinition -i "SELECTEDMEASUREFORMATSTRING()" --save
```
-`expression`, `ordinal`, and `formatStringDefinition` are the documented `CalculationItem` properties. Confirm the calculation-item child-path form with `te ls "Time Intelligence"` after creating the group, before scripting many items in a pipeline.
+`expression`, `ordinal`, and `formatStringDefinition` are the documented `CalculationItem` properties. Confirm the calculation-item child-path form with `te list "Time Intelligence"` after creating the group, before scripting many items in a pipeline.
Sources: [Calculated columns and measures (SQLBI)](https://www.sqlbi.com/articles/calculated-columns-and-measures-in-dax/), [DAX calculated columns vs Power Query (SQLBI)](https://www.sqlbi.com/articles/comparing-dax-calculated-columns-with-power-query-computed-columns/), [Data reduction - custom columns (MS Learn)](https://learn.microsoft.com/power-bi/guidance/import-modeling-data-reduction#preference-for-custom-columns), [Calculation groups (MS Learn)](https://learn.microsoft.com/analysis-services/tabular-models/calculation-groups), [Understanding calculation groups (SQLBI)](https://www.sqlbi.com/articles/understanding-calculation-groups/), [Controlling format strings in calculation groups (SQLBI)](https://www.sqlbi.com/articles/controlling-format-strings-in-calculation-groups/).
@@ -139,7 +139,7 @@ Define roles, set permissions, attach filters, then validate and deploy. Prefer
```bash
te add Roles/RegionManagers -t Role -m ./model --save
te set Roles/RegionManagers -q modelPermission -i Read --save
-# Dynamic filter: confirm the TablePermission add/path form with `te ls Roles/RegionManagers/TablePermissions`
+# Dynamic filter: confirm the TablePermission add/path form with `te list Roles/RegionManagers/TablePermissions`
te set Roles/RegionManagers/TablePermissions/Sales -q filterExpression -i "[Region] = USERPRINCIPALNAME()" --save
te validate -m ./model
te deploy ./model -s ws -d model --deploy-roles --deploy-role-members --force --ci github
diff --git a/skills/te-cli/references/te2-migration.md b/skills/te-cli/references/te2-migration.md
index eb02a7a..7dfce3a 100644
--- a/skills/te-cli/references/te2-migration.md
+++ b/skills/te-cli/references/te2-migration.md
@@ -38,7 +38,7 @@ te migrate --output-format json # machine-readable for codemods
| `-T ` | `--trx ` |
| `-B ` | `te save -o --serialization bim` |
| `-TMDL ` | `te save -o --serialization tmdl` (default format) |
-| `-F ` | `te save -o --serialization te-folder` (or `--deploy-full` after `-D`) |
+| `-F ` | `te save -o --serialization database.json` (or `--deploy-full` after `-D`) |
| `-Y` | `--deploy-partitions --skip-refresh-policy` |
| `-W` / `-E` | (default) |
| `-L ` (after `-D`) | `te auth login -u -p -t ` (prefer env vars) |
@@ -49,4 +49,3 @@ te migrate --output-format json # machine-readable for codemods
- `te deploy` prompts for confirmation. CI must pass `--force`.
- All commands support `--output-format json` for machine-readable output.
- No `start /wait` wrapper needed on Windows; it's a normal console binary.
-
diff --git a/skills/te-cli/references/workflows.md b/skills/te-cli/references/workflows.md
index 47487a8..391d956 100644
--- a/skills/te-cli/references/workflows.md
+++ b/skills/te-cli/references/workflows.md
@@ -51,10 +51,11 @@ te add _Measures -t Table --columns "_Measures:String" \
**Pre-validation errors** (fail before mutation):
- `--source-type calculated` paired with `-t Table` → use `-t CalculatedTable`
-- `--source-type m` on a model with a provider data source → remove the DS, or use `--source-type query`
- `--source-type m` on Compatibility Level < 1400 → upgrade the model
- `--source-type` combined with `--mode directlake` → DL/Entity partitions are picked automatically
+**Note:** `--source-type m` on a model that already has a provider data source is now supported (matches TE3 desktop's mixed-partition support); the CLI no longer rejects that combination.
+
**Updating an existing partition's M** after creation: `te set Sales/Partitions/Sales -q MExpression -i "" --save` (note `MExpression`, not `expression`, despite `te get` displaying the property as `expression`).
### Convert TMDL ↔ BIM ↔ PBIP
@@ -93,8 +94,8 @@ te refresh --table Sales --partition "Sales.2024" --type full --dry-run > refres
```bash
te deps --unused --hidden # discover candidates
-te rm Sales/UnusedMeasure --dry-run # confirm impact
-te rm Sales/UnusedMeasure --if-exists --save # idempotent removal
+te remove Sales/UnusedMeasure --dry-run # confirm impact
+te remove Sales/UnusedMeasure --if-exists --save # idempotent removal
```
### Mirror remote workspace for local editing
@@ -129,7 +130,7 @@ te test compare # detect
## Additional authoring workflows
-Modeling-driven recipes (mark a date table, calculation groups, RLS roles) live in semantic-modeling-practices.md, paired with the rationale for each. The recipes below are the remaining structural-object workflows. The `te` CLI is in preview; confirm any flag or path shape below with `te --help` (or `te ls ` to see the exact child-path form) before scripting it in a pipeline.
+Modeling-driven recipes (mark a date table, calculation groups, RLS roles) live in semantic-modeling-practices.md, paired with the rationale for each. The recipes below are the remaining structural-object workflows. The `te` CLI is in preview; confirm any flag or path shape below with `te --help` (or `te list ` to see the exact child-path form) before scripting it in a pipeline.
### Perspectives
@@ -139,7 +140,7 @@ Perspectives are saved field-list views. Create the perspective, then add tables
te add "Perspectives/Sales View" -t Perspective -m ./model --save
te add "Perspectives/Sales View/Sales" -m ./model --save # add the Sales table to the perspective
te add "Perspectives/Sales View/_Measures/Revenue" -m ./model --save # add a single measure
-te ls "Perspectives/Sales View" # confirm membership
+te list "Perspectives/Sales View" # confirm membership
```
### Translations and cultures