strata-cli 0.1.9 ā 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +26 -2
- data/lib/strata/cli/agent_mode.rb +26 -0
- data/lib/strata/cli/agent_output.rb +21 -0
- data/lib/strata/cli/generators/templates/AGENTS.md +457 -88
- data/lib/strata/cli/generators/templates/table.table_name.yml +7 -2
- data/lib/strata/cli/helpers/datasource_helper.rb +26 -0
- data/lib/strata/cli/main.rb +2 -5
- data/lib/strata/cli/sub_commands/audit.rb +132 -13
- data/lib/strata/cli/sub_commands/branch.rb +2 -0
- data/lib/strata/cli/sub_commands/create.rb +12 -0
- data/lib/strata/cli/sub_commands/datasource.rb +19 -3
- data/lib/strata/cli/sub_commands/deploy.rb +2 -0
- data/lib/strata/cli/sub_commands/project.rb +2 -0
- data/lib/strata/cli/sub_commands/table.rb +2 -0
- data/lib/strata/cli/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4995c9ab5a77b99effe743f6518204ffe8c2417e06f2f3acac57efe5f82cd041
|
|
4
|
+
data.tar.gz: 89c9a32ddf48ac1e4e68a92aa0f6f8acf6f37fbc9267a0134fddc3a34a859ed0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca85facb30bebc4b5ec05ca79b15e3e94434fa10122400baadab6b923700fa4117b39b5cc06fddb1ccbf791ee1d772d755e1618159a5957d761ac1851ead083f
|
|
7
|
+
data.tar.gz: 07e20501e723d426b03044996bd2a20748e4bd322fa71e43f6c193be8d0516a49672e0ed7acd94f81a451cc99c0c8b48b087b02af8f334b10e50157518df520e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.10] - 2026-05-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Agent mode (`--agent`)**: Structured JSON output for automation and agent workflows on read-only commands (`datasource tables`, `datasource meta`, `version`, and others). Interactive `create` flows return machine-readable errors with stable codes when agent mode is requested.
|
|
8
|
+
- **Audit field validation**: Model audit checks allowed field keys, expression shape (string shortcut or mapping with `sql`), and `format` types (string shortcuts and hash forms).
|
|
9
|
+
- **Audit import resolution**: Model checks resolve YAML imports via `YamlImportResolver` before validating table and relationship definitions.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **AGENTS.md template**: Updated project init template with semantic-model authoring guidance and `--agent` command usage.
|
|
14
|
+
|
|
3
15
|
## [0.1.9] - 2026-05-13
|
|
4
16
|
|
|
5
17
|
### Added
|
data/README.md
CHANGED
|
@@ -25,6 +25,30 @@ gem install strata-cli
|
|
|
25
25
|
|
|
26
26
|
> š” **Tip:** Run `strata COMMAND --help` for detailed help, options, and examples on any command. The CLI help system provides comprehensive documentation for each command.
|
|
27
27
|
|
|
28
|
+
## Agent mode (`--agent`)
|
|
29
|
+
|
|
30
|
+
Pass `--agent` on any command for structured JSON output and no interactive prompts (where the command supports it).
|
|
31
|
+
|
|
32
|
+
**Supported examples:**
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
strata datasource list --agent
|
|
36
|
+
strata datasource tables my_db --agent
|
|
37
|
+
strata datasource meta my_db orders --agent
|
|
38
|
+
strata table list --agent
|
|
39
|
+
strata audit all --agent
|
|
40
|
+
strata deploy --yes --agent
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Not supported** ā these commands are interactive generators; write YAML directly instead:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
strata create table [TABLE_PATH] # ā models/tbl.*.yml
|
|
47
|
+
strata create relation RELATION_PATH # ā models/rel.*.yml
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Use `strata datasource meta DS_KEY TABLE_NAME --agent` for column metadata when authoring table models.
|
|
51
|
+
|
|
28
52
|
<details>
|
|
29
53
|
<summary><strong>Project Management</strong></summary>
|
|
30
54
|
|
|
@@ -127,7 +151,7 @@ strata datasource exec my_db -f queries/analysis.sql
|
|
|
127
151
|
<summary><strong>Model Creation</strong></summary>
|
|
128
152
|
|
|
129
153
|
### `strata create table [TABLE_PATH]`
|
|
130
|
-
Create a semantic table model from a datasource table.
|
|
154
|
+
Create a semantic table model from a datasource table. **Not available with `--agent`** ā write `models/tbl.*.yml` directly or use `strata datasource meta` for schema metadata.
|
|
131
155
|
|
|
132
156
|
**Options:** `-d, --datasource DS_KEY`
|
|
133
157
|
|
|
@@ -142,7 +166,7 @@ strata create table contact/dse.call_center_d # With schema
|
|
|
142
166
|
**Process:** Checks table existence ā Fetches metadata ā AI suggests fields ā Interactive editor ā Generates model file
|
|
143
167
|
|
|
144
168
|
### `strata create relation RELATION_PATH`
|
|
145
|
-
Create a relation (join) definition file.
|
|
169
|
+
Create a relation (join) definition file. **Not available with `--agent`** ā write `models/rel.*.yml` directly.
|
|
146
170
|
|
|
147
171
|
**Options:** `-d, --datasource DS_KEY`
|
|
148
172
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "agent_output"
|
|
4
|
+
|
|
5
|
+
module Strata
|
|
6
|
+
module CLI
|
|
7
|
+
module AgentMode
|
|
8
|
+
def self.included(base)
|
|
9
|
+
base.class_option :agent, type: :boolean, default: false,
|
|
10
|
+
desc: "Agent mode: structured output, no interactive prompts"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def agent_mode?
|
|
14
|
+
value = options[:agent]
|
|
15
|
+
value = options["agent"] if value.nil?
|
|
16
|
+
value == true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def reject_agent_mode!(message, code: "agent_mode_unsupported")
|
|
20
|
+
return unless agent_mode?
|
|
21
|
+
|
|
22
|
+
AgentOutput.emit_error(message, code: code)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Strata
|
|
6
|
+
module CLI
|
|
7
|
+
module AgentOutput
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def emit_json(data)
|
|
11
|
+
$stdout.puts(JSON.generate(data))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def emit_error(message, code: "agent_mode_unsupported", **metadata)
|
|
15
|
+
payload = {error: message, code: code}.merge(metadata)
|
|
16
|
+
warn(JSON.generate(payload))
|
|
17
|
+
exit(1)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -1,136 +1,505 @@
|
|
|
1
1
|
# Strata Semantic Model Project
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
You are authoring a **semantic model** ā version-controlled YAML in this repo (`models/tbl.*.yml`, `models/rel.*.yml`, datasources). That is not one-off SQL, warehouse ETL, or a standalone script.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
| Term | Meaning in Strata |
|
|
6
|
+
|------|-------------------|
|
|
7
|
+
| **Semantic model** | What you edit in git ā tables, fields, relationships, migrations |
|
|
8
|
+
| **Semantic layer** | What exists on the server **after deploy** ā the governed dimensions, measures, and joins users query |
|
|
6
9
|
|
|
10
|
+
After **`strata deploy`**, this projectās model becomes (or updates) the **semantic layer** on the Strata server for that branch. The **Strata web app** charts and reports run against that layer, not against raw warehouse tables.
|
|
11
|
+
|
|
12
|
+
## What this model powers (end-to-end)
|
|
13
|
+
|
|
14
|
+
| Stage | What happens |
|
|
15
|
+
|-------|----------------|
|
|
16
|
+
| **You (agent + human)** | Edit the **semantic model** in `models/`; run `strata audit` |
|
|
17
|
+
| **Human** | `strata deploy` publishes the model to the **Strata server** (per git branch) |
|
|
18
|
+
| **Strata server** | Builds join universes, plans queries, runs SQL against datasources |
|
|
19
|
+
| **Strata web app** | Analysts query the deployed **semantic layer** ā charting, reporting, filters, exploration by field **name** |
|
|
20
|
+
|
|
21
|
+
Field and table **names**, joins, and measure definitions affect **live dashboards and saved reports**, not only CLI or git. Breaking naming, duplicates, or joins can break production content after deploy. YAML is consumed by deploy on the server ā it is **not** ad-hoc warehouse SQL run in isolation.
|
|
22
|
+
|
|
23
|
+
Read this file end-to-end before creating or editing `models/**/*.yml`. Do not mirror warehouse DDL or ad-hoc SQL style; follow the contracts below.
|
|
24
|
+
|
|
25
|
+
**Do not infer Strata behavior from warehouse SQL or generic BI tools.** Rules in this file are authoritative. Use the [official documentation index](#official-documentation) only when you need depth beyond what is inlined here.
|
|
26
|
+
|
|
27
|
+
## Critical rules
|
|
28
|
+
|
|
29
|
+
These are non-negotiable. Violations break deploy, query planning, or production reports.
|
|
30
|
+
|
|
31
|
+
1. **Every field `name` is unique project-wide** ā one name = one dimension or measure entity across all `tbl.*.yml` files. Strata has no `table.field` namespaces; bracket references like `[Total Revenue]` resolve by name alone.
|
|
32
|
+
2. **`many_to_many` join cardinality is not supported.** Use a junction/bridge table with two relationships (`one_to_many` + `many_to_one`) instead.
|
|
33
|
+
3. **Measures on unrelated detail facts must have distinct names** ā e.g. `Store Gross Sales`, `Catalog Gross Sales`, not one `Gross Sales` on three channel facts. See [Naming conventions](#naming-conventions).
|
|
34
|
+
4. **Dimensions may share names** when the business role is the same; Strata picks among tables at query time. **Measures do not combine that way** ā duplicate measure names attach more SQL to the same measure entity (double-count risk).
|
|
35
|
+
5. **Cross-fact totals use compound measures or blending** ā not the same measure name on multiple facts. See [Combined metrics across facts](#combined-metrics-across-facts).
|
|
36
|
+
|
|
37
|
+
## Unsupported or impossible requirements
|
|
38
|
+
|
|
39
|
+
**If the user asks for something Strata does not support, say so clearly and do not edit `models/**/*.yml` to approximate it.** A broken or misleading semantic model is worse than no change.
|
|
40
|
+
|
|
41
|
+
### Before writing YAML
|
|
42
|
+
|
|
43
|
+
1. **Classify the request:** supported in Strata Ā· supported with a different pattern Ā· unsupported Ā· unknown.
|
|
44
|
+
2. **If unsupported or unknown:** explain the limitation, why a workaround would fail (deploy, double-count, invalid cardinality, invented keys), and offer **compliant alternatives** if any exist.
|
|
45
|
+
3. **Wait for the user to choose** a supported approach (or to change the requirement) before proposing file changes.
|
|
46
|
+
4. **Never ship ābest effortā YAML** that you expect `audit` or deploy to reject, or that violates [Critical rules](#critical-rules) ā fix the design in conversation first.
|
|
47
|
+
|
|
48
|
+
### What to tell the user
|
|
49
|
+
|
|
50
|
+
Use plain language, for example:
|
|
51
|
+
|
|
52
|
+
- āStrata does not support X. Doing Y in YAML would not work because ā¦ā
|
|
53
|
+
- āSupported alternative: ⦠(trade-off: ā¦)ā
|
|
54
|
+
- āIām not sure Strata supports X; I wonāt change the model until we confirm ā see [official documentation](#official-documentation) or your Strata admin.ā
|
|
55
|
+
|
|
56
|
+
### Do not do these when blocked
|
|
57
|
+
|
|
58
|
+
- Invent YAML keys or properties not in Strataās schema (`custom_join`, `blend_measures`, etc.).
|
|
59
|
+
- Use duplicate measure names, fake dimensions, or `many_to_many` joins as workarounds.
|
|
60
|
+
- Paste full ad-hoc report SQL into `expression.sql` and call it a semantic model.
|
|
61
|
+
- Rename or migrate entities to āmakeā an unsupported shape fit.
|
|
62
|
+
- Silently implement something that only works in raw warehouse SQL, not in Strataās planner.
|
|
63
|
+
|
|
64
|
+
### Common unsupported asks ā response pattern
|
|
65
|
+
|
|
66
|
+
| User ask | Why it fails | Compliant direction (if any) |
|
|
67
|
+
|----------|--------------|------------------------------|
|
|
68
|
+
| `many_to_many` join between two tables | Not supported | Junction table + two `rel` entries |
|
|
69
|
+
| One measure name on store + catalog + web facts | One measure entity, double-count risk | Distinct measures + [compound measure](#combined-metrics-across-facts) |
|
|
70
|
+
| āCombine metrics by reusing the same nameā | Not how Strata merges facts | Compound measure or blending on shared dimensions |
|
|
71
|
+
| Cross-datasource compound measure | Same datasource required | Model per datasource or separate metrics |
|
|
72
|
+
| Datasource swap migration | Not supported | Rename datasource; human migration plan |
|
|
73
|
+
| Snapshot-style metric on a flow fact without `snapshot_date` | Wrong measure type | Normal additive measure, or proper snapshot table setup |
|
|
74
|
+
| Requirement needs engine feature you cannot verify | Risk of invalid model | Stop; cite docs or ask human ā do not guess |
|
|
75
|
+
|
|
76
|
+
When `strata audit all --agent` reports errors you cannot resolve within Strataās rules, **stop and report the errors to the user** ā do not keep patching YAML until the model drifts further from a valid design.
|
|
77
|
+
|
|
78
|
+
## Order of operations for agents
|
|
79
|
+
|
|
80
|
+
Follow this sequence. Do not skip steps or reorder deploy before audit.
|
|
81
|
+
|
|
82
|
+
| Step | Who | Action |
|
|
83
|
+
|------|-----|--------|
|
|
84
|
+
| 1 | Agent | `strata datasource list --agent` ā `tables` ā `meta` (discovery) |
|
|
85
|
+
| 2 | Agent | `strata table list --agent` (existing models, if any) |
|
|
86
|
+
| 3 | Agent | Classify tables; check request is [supported](#unsupported-or-impossible-requirements); propose field names and joins (or explain why not) |
|
|
87
|
+
| 4 | Human | Approve proposal (required before any file writes) |
|
|
88
|
+
| 5 | Agent | Write `models/**/*.yml` (and `migrations/*.yml` if renaming ā see [Renames and swaps](#renames-swaps-and-production-branch)) |
|
|
89
|
+
| 6 | Agent | `strata audit all --agent` ā **required after every model change** |
|
|
90
|
+
| 7 | Human | `strata deploy` on the intended git branch (deploy runs audits again) |
|
|
91
|
+
|
|
92
|
+
**Agents must not run:** `strata deploy`, `strata datasource add`, interactive `strata create table` / `strata create relation` / `strata create migration`, or writing secrets into `.strata`.
|
|
93
|
+
|
|
94
|
+
**Branches:** Model on feature branches; deploy to the matching Strata server branch for staging. Production deploys typically use `production_branch` in `project.yml` (default `main`). Renames and swaps that affect production query names should happen on the production branch ā see [Renames and swaps](#renames-swaps-and-production-branch).
|
|
95
|
+
|
|
96
|
+
## How the semantic layer maps to the warehouse
|
|
97
|
+
|
|
98
|
+
| Concept | In your YAML | Not the same as |
|
|
99
|
+
|--------|----------------|-----------------|
|
|
100
|
+
| Table users query | `name` in `tbl.*.yml` | `physical_name` (warehouse table) |
|
|
101
|
+
| Column in a table | `expression.sql` on each field | Bare column string or `physical_name.column` |
|
|
102
|
+
| Join endpoints | `left` / `right` (logical table names) | Table names inside join `sql` |
|
|
103
|
+
| Join condition | `sql` using `left.` and `right.` | `store_sales.col = date_dim.col` |
|
|
104
|
+
| Second FK to same dimension | New logical `tbl` (role-playing) + join to that name | Two `rel` entries with same `left` + `right` |
|
|
105
|
+
|
|
106
|
+
## Deploy contracts (required on every change)
|
|
107
|
+
|
|
108
|
+
Every `tbl` field and every `rel` join must satisfy **all** of these before deploy will succeed:
|
|
109
|
+
|
|
110
|
+
1. **Field `expression`** is a non-empty SQL string shortcut (`expression: order_id`, `expression: sum(amount)`) or a mapping with non-empty `sql`. Use the mapping form when you need `lookup`, `primary_key`, or `array`.
|
|
111
|
+
2. **Join `sql` uses only `left.` and `right.`** ā column names from field definitions on those tables. Never warehouse prefixes (`orders.`, `date_dim.`, `store_sales.`).
|
|
112
|
+
3. **At most one join per logical table pair** in a branch. If a fact has two FKs to the same physical dimension (e.g. sold date and ship date both ā `date_dim`), create **separate logical tables** (role-playing dimensions) and join to each distinct `name`.
|
|
113
|
+
4. **`left` / `right` in rel files match `name` in `tbl.*.yml` exactly** (same spelling and casing as the table model).
|
|
114
|
+
|
|
115
|
+
Run `strata audit all --agent` after edits; a human runs `strata deploy`.
|
|
116
|
+
|
|
117
|
+
## CLI for agents
|
|
118
|
+
|
|
119
|
+
Append `--agent` to every `strata` command (structured output, no prompts):
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
strata datasource list --agent
|
|
123
|
+
strata datasource tables MY_DS --agent
|
|
124
|
+
strata datasource meta MY_DS TABLE_NAME --agent
|
|
125
|
+
strata table list --agent
|
|
126
|
+
strata audit all --agent
|
|
7
127
|
```
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
128
|
+
|
|
129
|
+
**Do not run:** `strata create table`, `strata create relation` (interactive generators). Write `models/tbl.*.yml` and `models/rel.*.yml` directly instead.
|
|
130
|
+
|
|
131
|
+
If multiple datasources exist, pass `DS_KEY` on `tables` and `meta`.
|
|
132
|
+
|
|
133
|
+
## Datasources ā human-in-the-loop (secrets)
|
|
134
|
+
|
|
135
|
+
If the CLI returns `no_datasources`, ask the user to run `strata datasource add [ADAPTER]` in a terminal. Do not create `datasources.yml` entries or fill `.strata` credentials yourself.
|
|
136
|
+
|
|
137
|
+
## Human-in-the-loop before file writes
|
|
138
|
+
|
|
139
|
+
Do **not** silently bulk-write model files. Before any `models/**/*.yml` change:
|
|
140
|
+
|
|
141
|
+
0. **If the request is unsupported** ā follow [Unsupported or impossible requirements](#unsupported-or-impossible-requirements); do not proceed to file writes until the user picks a supported approach.
|
|
142
|
+
1. **Describe intent** ā tables, fields, joins, and role-playing tables if needed (e.g. separate **Catalog Ship Date** when catalog sales has both sold-date and ship-date FKs).
|
|
143
|
+
2. **Explain semantic impact** ā which **dimension** names are intentionally shared vs which **measure** names stay **distinct per fact**; new join paths; any proposed **compound measures** (including **which host table** defines dimensionality); any **migrations** (renames/swaps) and whether work is on **production branch**; which logical tables are created vs updated.
|
|
144
|
+
3. **Wait for explicit approval**, then write files and run `strata audit all --agent`.
|
|
145
|
+
|
|
146
|
+
## Workflow
|
|
147
|
+
|
|
148
|
+
Same steps as [Order of operations for agents](#order-of-operations-for-agents). In the proposal (step 3ā4), include:
|
|
149
|
+
|
|
150
|
+
- Table classification: **fact**, **dimension**, or **aggregate/summary**
|
|
151
|
+
- **Unique measure name per unrelated detail fact**; shared dimension names only where the role matches
|
|
152
|
+
- `tbl.*.yml` and `rel.*.yml` drafts that satisfy [Deploy contracts](#deploy-contracts-required-on-every-change) and naming rules below
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Table models ā `models/tbl.<name>.yml`
|
|
157
|
+
|
|
158
|
+
```yaml
|
|
159
|
+
datasource: "<datasource_key_or_name>"
|
|
160
|
+
name: "<logical_name>" # Required ā used in rel left/right and queries
|
|
161
|
+
physical_name: "<warehouse_table>" # Required ā actual table in the database
|
|
162
|
+
cost: 10
|
|
163
|
+
|
|
164
|
+
fields:
|
|
165
|
+
- type: dimension
|
|
166
|
+
name: "Order ID"
|
|
167
|
+
data_type: bigint
|
|
168
|
+
expression:
|
|
169
|
+
sql: order_id # Column or expression fragment for this table
|
|
170
|
+
lookup: true # Typical for filterable dimensions
|
|
171
|
+
primary_key: true # Optional ā business/surrogate key
|
|
172
|
+
|
|
173
|
+
- type: measure
|
|
174
|
+
name: "Store Revenue" # Unique per fact ā not reused on other fact tables
|
|
175
|
+
data_type: decimal
|
|
176
|
+
expression:
|
|
177
|
+
sql: sum(ss_sales_price) # Aggregation required for measures
|
|
15
178
|
```
|
|
16
179
|
|
|
17
|
-
|
|
180
|
+
**Field rules**
|
|
18
181
|
|
|
19
|
-
|
|
182
|
+
- `data_type`: `string`, `integer`, `bigint`, `decimal`, `date`, `date_time`, `boolean` (map from `normalized_data_type` in `datasource meta`).
|
|
183
|
+
- `expression` keys: `sql` (required), `lookup`, `primary_key`, `array` (optional booleans).
|
|
184
|
+
- Optional: `description`, `grains` (date/time), `format`, `synonyms`, `imports` of other tbl files.
|
|
20
185
|
|
|
21
|
-
|
|
186
|
+
**Date/time grains** (optional):
|
|
22
187
|
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
strata datasource list # List configured datasources
|
|
26
|
-
strata datasource tables [DS_KEY] # Browse physical tables in a datasource
|
|
27
|
-
strata datasource meta DS_KEY TABLE_NAME # Show columns for a physical table
|
|
188
|
+
```yaml
|
|
189
|
+
grains: [day, week, month, quarter, year]
|
|
28
190
|
```
|
|
29
191
|
|
|
30
|
-
|
|
192
|
+
---
|
|
31
193
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# sales/orders ā models/sales/tbl.orders.yml
|
|
37
|
-
# dw.fact_orders ā models/tbl.dw.fact_orders.yml (schema-prefixed)
|
|
194
|
+
## Relationship models ā `models/rel.<name>.yml`
|
|
195
|
+
|
|
196
|
+
```yaml
|
|
197
|
+
datasource: "<datasource_key_or_name>"
|
|
38
198
|
|
|
39
|
-
|
|
199
|
+
orders_to_customers:
|
|
200
|
+
left: "Orders" # Must match tbl name ā many side for many_to_one
|
|
201
|
+
right: "Customers" # Must match tbl name ā one side
|
|
202
|
+
sql: "left.customer_id = right.id"
|
|
203
|
+
cardinality: many_to_one
|
|
40
204
|
```
|
|
41
205
|
|
|
42
|
-
|
|
206
|
+
**Join rules**
|
|
43
207
|
|
|
44
|
-
|
|
208
|
+
- YAML `left` / `right`: logical table `name` values from `tbl.*.yml`.
|
|
209
|
+
- `sql`: equality using **`left.<column>`** and **`right.<column>`** only ā use the same column names as in each tableās field `expression.sql`.
|
|
210
|
+
- `cardinality`: `many_to_one`, `one_to_many`, or `one_to_one` (as appropriate).
|
|
211
|
+
- Optional: `join: inner` (default) or `left` / `right`.
|
|
45
212
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
213
|
+
### Role-playing dimensions (multiple FKs to one physical table)
|
|
214
|
+
|
|
215
|
+
When one fact table has **more than one foreign key** to the same physical dimension (common with `date_dim`: sold date, ship date, etc.), you need **one logical table per role**, each with its own join:
|
|
216
|
+
|
|
217
|
+
| Wrong | Right |
|
|
218
|
+
|-------|--------|
|
|
219
|
+
| Two rel entries: `Catalog Sales` ā `Date` (sold) and `Catalog Sales` ā `Date` (ship) | `Catalog Sales` ā `Date` (sold) and `Catalog Sales` ā `Catalog Ship Date` (ship) |
|
|
220
|
+
| Single `tbl.date.yml` reused for both roles without planning | Add `tbl.catalog_ship_date.yml` with `name: Catalog Ship Date`, same `physical_name: date_dim`, fields aligned to date role |
|
|
221
|
+
|
|
222
|
+
Example pattern:
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
# tbl ā two logical tables, one physical date_dim
|
|
226
|
+
# tbl.date.yml ā name: Date
|
|
227
|
+
# tbl.catalog_ship_date.yml ā name: Catalog Ship Date, physical_name: date_dim
|
|
228
|
+
|
|
229
|
+
# rel ā distinct right table per FK
|
|
230
|
+
catalog_sales_sold_date:
|
|
231
|
+
left: "Catalog Sales"
|
|
232
|
+
right: "Date"
|
|
233
|
+
sql: "left.cs_sold_date_sk = right.d_date_sk"
|
|
234
|
+
cardinality: many_to_one
|
|
235
|
+
|
|
236
|
+
catalog_sales_ship_date:
|
|
237
|
+
left: "Catalog Sales"
|
|
238
|
+
right: "Catalog Ship Date"
|
|
239
|
+
sql: "left.cs_ship_date_sk = right.d_date_sk"
|
|
240
|
+
cardinality: many_to_one
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Before adding rel entries, list each factās FKs and confirm **no duplicate `(left, right)` pairs**. If ship and sold both target `Date`, add a role-playing tbl first.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Naming conventions
|
|
248
|
+
|
|
249
|
+
### Dimensions (global ā reuse when the role is the same)
|
|
250
|
+
|
|
251
|
+
- **Dimension names are global** across the project ā same name on multiple tables = same concept; Strata picks the best table at query time.
|
|
252
|
+
- **Reuse consistent names** for shared dimensions (e.g. "Customer ID", "Date") where the role is the same.
|
|
253
|
+
- **Prefix role-specific dimensions** when the same physical column means different things (e.g. "Catalog Ship Date" vs "Date").
|
|
254
|
+
- **Prefix ambiguous attributes** on dimension tables (e.g. "Item Color" not "Color" on an item dimension).
|
|
255
|
+
|
|
256
|
+
### Measures (unique ā do not treat like dimensions)
|
|
257
|
+
|
|
258
|
+
- **Project-wide uniqueness:** every measure `name` must be unique across the entire semantic layer (same rule as dimensions). There is only one `Semantic::Field` per name per branch; deploy reuses it when the same name appears in another `tbl.*.yml`.
|
|
259
|
+
- **Default:** one measure name per **unrelated detail fact**. Example (wrong): `Gross Sales` on `store_sales`, `catalog_sales`, and `web_sales`. Example (right): `Store Gross Sales Amount`, `Catalog Gross Sales Amount`, `Web Gross Sales Amount`.
|
|
260
|
+
- **One measure name = one measure entity.** Putting the same name on another fact table adds that tableās SQL to the **same** measure ā it does **not** create a second metric and can cause **double counting** or ambiguous routing.
|
|
261
|
+
- **Exception ā aggregate/summary tables only:** reusing a measure name is appropriate when the second table is an **alternate physical representation** of the **same** metric (rollup / pre-aggregate), routed via table **`cost`** and/or **`partitions`** ā not when modeling three separate channel facts. Do **not** put the same additive flow measure on both a detail fact and a rollup fact without that routing design.
|
|
262
|
+
- **Similar warehouse column names do not imply one shared measure.** `ss_sales_price` and `cs_ext_sales_price` are different facts unless you have explicit rollup routing as above.
|
|
263
|
+
- **Prefix or qualify by fact/table** (`Store ā¦`, `Catalog ā¦`, `Web ā¦`) or use distinct business names (`order_revenue` vs `subscription_revenue`).
|
|
264
|
+
- **Do not āfixā duplicate measure names with rename migrations** ā use distinct names or a [compound measure](#combined-metrics-across-facts) instead.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Multi-fact warehouses (star schema)
|
|
269
|
+
|
|
270
|
+
Typical warehouses (e.g. TPC-DS on Athena) have **many fact tables** and shared dimensions. Before naming fields:
|
|
271
|
+
|
|
272
|
+
1. **Inventory tables** ā label each as fact, dimension, or aggregate/summary (pre-aggregated rollups).
|
|
273
|
+
2. **One grain per fact** ā e.g. `store_sales` = store channel transactions; do not model the same additive flow metric on both a **detail** fact and a **rollup** fact unless the rollup is explicitly non-additive or routed via table `cost` / `partitions` (see advanced links below).
|
|
274
|
+
3. **Do not copy a synthetic dimension onto every fact** (e.g. "Sales Channel" on store, catalog, and web facts) to justify reusing one measure name ā that does **not** fix measure uniqueness or double-count risk. Put channel only where that factās grain truly includes channel.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Combined metrics across facts
|
|
279
|
+
|
|
280
|
+
| Wrong | Right |
|
|
281
|
+
|-------|--------|
|
|
282
|
+
| Same measure name on `store_sales`, `catalog_sales`, `web_sales` | Distinct measure per fact |
|
|
283
|
+
| Expect Strata to auto-sum identical names | Explicit **compound measure** (or blending via shared **dimensions**, not duplicate measure names) |
|
|
284
|
+
|
|
285
|
+
To report a **total across channels/facts**, define separate measures per fact, then optionally one compound measure:
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
# On one table (or where the compound is defined), same datasource:
|
|
289
|
+
- type: measure
|
|
290
|
+
name: Total Gross Sales
|
|
291
|
+
data_type: decimal
|
|
292
|
+
expression:
|
|
293
|
+
sql: ([Store Gross Sales Amount] + [Catalog Gross Sales Amount] + [Web Gross Sales Amount])
|
|
55
294
|
```
|
|
56
295
|
|
|
57
|
-
|
|
296
|
+
Bracket names must match deployed measure names exactly. Compound measures are resolved at query time; all referenced measures must be in the **same datasource** and reachable in a valid universe. No circular references (`A` ā `B` ā `A`).
|
|
297
|
+
|
|
298
|
+
### Host table and dimensionality
|
|
299
|
+
|
|
300
|
+
Define compound measures on the **`tbl.*.yml` whose universe should govern the metric**:
|
|
301
|
+
|
|
302
|
+
- Which **dimensions** can group the compound measure
|
|
303
|
+
- How **automatic data blending** applies when referenced measures live on different tables in the same datasource
|
|
304
|
+
|
|
305
|
+
If referenced measures are not joinable in **one universe** from that host table, query planning fails. Propose the host table with the human reviewer (e.g. compound on **Store Sales** exposes store-centric join paths; compound on a shared dimension table exposes different dimensions).
|
|
306
|
+
|
|
307
|
+
**Use calculations (compound measures), not shared measure names** across unrelated facts.
|
|
308
|
+
|
|
309
|
+
**Automatic data blending** merges results on shared **dimensions** (often via `extended_blend_group`) ā not by reusing the same measure name on multiple fact tables. See [extended blending](https://strata.do/developer-docs/developer-guide/semantic-model/expressions/extended-blending) and the [glossary](https://strata.do/developer-docs/developer-guide/getting-started/glossary).
|
|
58
310
|
|
|
59
|
-
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
## Snapshot measures
|
|
314
|
+
|
|
315
|
+
**Use for** point-in-time **state** ā inventory on hand, account balances, membership counts at period boundaries. **Not for** additive flows over time (revenue, units sold) ā use normal measures with `sum(...)`.
|
|
316
|
+
|
|
317
|
+
**Requirements:**
|
|
318
|
+
|
|
319
|
+
1. Table sets `snapshot_date: <Date dimension name>` (dimension on same table or unambiguous in the tableās universe).
|
|
320
|
+
2. Measure sets `snapshot: ending` (value at end of period) or `snapshot: beginning` (value at start).
|
|
60
321
|
|
|
61
322
|
```yaml
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# Optional
|
|
68
|
-
description: ""
|
|
69
|
-
snapshot_date: "<dimension_name>" # For snapshot/inventory tables only
|
|
70
|
-
tags: []
|
|
71
|
-
imports: # Inherit fields from other YAML files
|
|
72
|
-
- "../shared/common_fields.yml"
|
|
323
|
+
name: Inventory
|
|
324
|
+
physical_name: inventory
|
|
325
|
+
datasource: warehouse
|
|
326
|
+
snapshot_date: Date
|
|
73
327
|
|
|
74
328
|
fields:
|
|
75
|
-
- type: dimension
|
|
76
|
-
name:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
synonyms: [] # 0ā3 alternative names for AI search
|
|
329
|
+
- type: dimension
|
|
330
|
+
name: Date
|
|
331
|
+
data_type: date
|
|
332
|
+
expression:
|
|
333
|
+
sql: snapshot_date
|
|
81
334
|
|
|
82
335
|
- type: measure
|
|
83
|
-
name:
|
|
84
|
-
data_type:
|
|
85
|
-
|
|
336
|
+
name: Ending Inventory
|
|
337
|
+
data_type: integer
|
|
338
|
+
snapshot: ending
|
|
339
|
+
expression:
|
|
340
|
+
sql: sum(quantity)
|
|
86
341
|
```
|
|
87
342
|
|
|
88
|
-
|
|
343
|
+
When users group by a time period, the engine picks the last (`ending`) or first (`beginning`) snapshot in each bucket ā not a sum of every row in the period.
|
|
344
|
+
|
|
345
|
+
More detail: [snapshot measures](https://strata.do/developer-docs/developer-guide/semantic-model/fields/measures/snapshot)
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Inclusion measures
|
|
89
350
|
|
|
90
|
-
|
|
351
|
+
**Problem:** medians and averages are wrong when the stored grain is finer than the statistic you need ā e.g. `median(hours)` over `(day, account, title)` rows is not āmedian account hours per day.ā
|
|
352
|
+
|
|
353
|
+
**Pattern:** two-step aggregation ā inner `expression.sql`, extra dimensions in `inclusions.dimensions`, outer `inclusions.aggregation`.
|
|
354
|
+
|
|
355
|
+
**Use when:** medians, percentiles, or averages that need an **intermediate grain** (e.g. per account per day) before rolling up to the query grain.
|
|
91
356
|
|
|
92
|
-
**Complex `expression` form:**
|
|
93
357
|
```yaml
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
358
|
+
- type: measure
|
|
359
|
+
name: Daily Median Account View Hours
|
|
360
|
+
data_type: decimal
|
|
361
|
+
inclusions:
|
|
362
|
+
filter: apply
|
|
363
|
+
aggregation: percentile_cont(0.5) WITHIN GROUP (ORDER BY @exp)
|
|
364
|
+
dimensions:
|
|
365
|
+
- Account ID
|
|
366
|
+
expression:
|
|
367
|
+
sql: sum(hours)
|
|
99
368
|
```
|
|
100
369
|
|
|
101
|
-
|
|
370
|
+
Inner: `sum(hours)` per `(day, account)`. Outer: median of those account totals per `day`.
|
|
371
|
+
|
|
372
|
+
More detail: [inclusions](https://strata.do/developer-docs/developer-guide/advanced/inclusions)
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Exclusion measures
|
|
377
|
+
|
|
378
|
+
**Two independent controls:**
|
|
379
|
+
|
|
380
|
+
- **`exclusion_type`** ā which dimensions may **group** the measure (`exclude`, `exclude_all_except`, `exclude_all`).
|
|
381
|
+
- **`filter` on each exclusion entry** ā how **filters** on those entities apply (`apply`, `ignore`, `only`).
|
|
382
|
+
|
|
383
|
+
**Use when:** grand totals, metrics that must not break down by certain dimensions, or revenue that should ignore a dimension for grouping while still filtering normally.
|
|
384
|
+
|
|
102
385
|
```yaml
|
|
103
|
-
|
|
386
|
+
- type: measure
|
|
387
|
+
name: Revenue Excluding Return Status
|
|
388
|
+
data_type: decimal
|
|
389
|
+
exclusion_type: exclude
|
|
390
|
+
exclusions:
|
|
391
|
+
- type: dimension
|
|
392
|
+
filter: apply
|
|
393
|
+
entities:
|
|
394
|
+
- Return Status
|
|
395
|
+
expression:
|
|
396
|
+
sql: sum(amount)
|
|
104
397
|
```
|
|
105
398
|
|
|
106
|
-
|
|
399
|
+
`Return Status` will not appear in the grouping set for this measure even if the user adds it to the query; other dimensions still group normally.
|
|
400
|
+
|
|
401
|
+
More detail: [exclusions](https://strata.do/developer-docs/developer-guide/advanced/exclusions)
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Common mistakes (anti-patterns)
|
|
406
|
+
|
|
407
|
+
| Anti-pattern | Why it fails |
|
|
408
|
+
|--------------|----------------|
|
|
409
|
+
| `Gross Sales` on store + catalog + web facts | One measure entity, multiple fact SQLs ā double-count / ambiguous routing |
|
|
410
|
+
| "Sales Channel" dimension added to every fact | Does not replace unique measure names per fact |
|
|
411
|
+
| Identical column labels in the warehouse ā one measure name | Warehouse naming ā one Strata metric |
|
|
412
|
+
| Assuming global dimension rules apply to measures | Dimensions are shared by name; measures are **not** |
|
|
413
|
+
| Rename/swap on a feature branch without team coordination | Production saved queries still reference old names; branch may not match production entities |
|
|
414
|
+
| Renaming to deduplicate measure names across facts | Use distinct names or compound measures ā migrations are for intentional renames, not modeling fixes |
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Advanced features (quick reference)
|
|
419
|
+
|
|
420
|
+
| Feature | Use when |
|
|
421
|
+
|---------|----------|
|
|
422
|
+
| [Compound measures](https://strata.do/developer-docs/developer-guide/semantic-model/fields/measures/compound) | Formula across named measures; host table sets dimensionality |
|
|
423
|
+
| [Snapshot measures](https://strata.do/developer-docs/developer-guide/semantic-model/fields/measures/snapshot) | Period-boundary state (inventory, balances) ā see [Snapshot measures](#snapshot-measures) |
|
|
424
|
+
| [Inclusions](https://strata.do/developer-docs/developer-guide/advanced/inclusions) | Median/percentile/avg needing intermediate grain ā see [Inclusion measures](#inclusion-measures) |
|
|
425
|
+
| [Exclusions](https://strata.do/developer-docs/developer-guide/advanced/exclusions) | Control grouping and filters per dimension ā see [Exclusion measures](#exclusion-measures) |
|
|
426
|
+
| Table `cost` | Lower cost = preferred table when multiple tables can answer; use for rollup vs detail routing |
|
|
427
|
+
| [Partitions](https://strata.do/developer-docs/developer-guide/advanced/partitions) | Table only has subset of data ā `between` (date range) or `in_list`; planner routes when partition matches |
|
|
428
|
+
| [Extended blending](https://strata.do/developer-docs/developer-guide/semantic-model/expressions/extended-blending) | Blend dimensions across tables in same datasource ā not duplicate measure names |
|
|
429
|
+
| [Imports](https://strata.do/developer-docs/developer-guide/semantic-model/imports) | Reuse field definitions across `tbl` files |
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
## Renames, swaps, and production branch
|
|
434
|
+
|
|
435
|
+
Saved reports and dashboards reference field and table **names**. Renaming without a migration breaks production queryables.
|
|
436
|
+
|
|
437
|
+
### Rename (`type: rename`, `hook: pre`)
|
|
438
|
+
|
|
439
|
+
Runs **before** YAML is processed. Old name is rewritten to new name; deploy updates saved references.
|
|
107
440
|
|
|
108
441
|
```yaml
|
|
109
|
-
|
|
442
|
+
- type: rename
|
|
443
|
+
hook: pre
|
|
444
|
+
entity: measure
|
|
445
|
+
from: Old Revenue
|
|
446
|
+
to: Store Revenue
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Supported entities: `dimension`, `measure`, `table`, `datasource`.
|
|
110
450
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
451
|
+
### Swap (`type: swap`, `hook: post`)
|
|
452
|
+
|
|
453
|
+
Runs **after** all definitions load. Replaces references from entity A to entity B when **both** exist. Datasource swap is not supported.
|
|
454
|
+
|
|
455
|
+
```yaml
|
|
456
|
+
- type: swap
|
|
457
|
+
hook: post
|
|
458
|
+
entity: measure
|
|
459
|
+
from: Legacy Revenue
|
|
460
|
+
to: Store Revenue
|
|
116
461
|
```
|
|
117
462
|
|
|
118
|
-
|
|
463
|
+
### Production branch rule
|
|
119
464
|
|
|
120
|
-
|
|
121
|
-
- **Use consistent names** for shared dimensions (e.g., "Customer ID", "Order Date") across all table models.
|
|
122
|
-
- **Prefix ambiguous dimensions** from dimension tables: a `color` column on an `item_dim` table should be named `Item Color`, not `Color`.
|
|
465
|
+
**Only rename or swap on `production_branch`** (in `project.yml`, usually `main`) when the change must align with production queryables. On other branches, production may still reference old names that do not exist on your branch.
|
|
123
466
|
|
|
124
|
-
|
|
467
|
+
Deploy **migration files and updated YAML in the same deployment**. Update `tbl.*.yml` references to match renames in the same change.
|
|
468
|
+
|
|
469
|
+
**Agents:** propose migration YAML under `migrations/` and matching model edits; do **not** run interactive `strata create migration`. Flag renames for human review and confirm branch strategy.
|
|
470
|
+
|
|
471
|
+
More detail: [migrations](https://strata.do/developer-docs/developer-guide/cli/migrations)
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## Official documentation
|
|
125
476
|
|
|
126
|
-
|
|
127
|
-
2. `strata create table <path>` ā generate and review a semantic model
|
|
128
|
-
3. Edit the generated `tbl.*.yml` directly for fine-tuning
|
|
129
|
-
4. `strata audit all` ā validate before deploying
|
|
130
|
-
5. `strata deploy` ā push to Strata server
|
|
477
|
+
**Primary:** this `AGENTS.md` ā follow inline rules first.
|
|
131
478
|
|
|
132
|
-
|
|
479
|
+
**Optional bulk load:** fetch https://strata.do/developer-docs/llms.txt when implementing a topic not fully covered here (complete Strata doc export for AI agents).
|
|
133
480
|
|
|
134
|
-
|
|
481
|
+
**Curated links** (use for examples and edge cases after reading the matching section above):
|
|
135
482
|
|
|
136
|
-
|
|
483
|
+
| Topic | URL |
|
|
484
|
+
|-------|-----|
|
|
485
|
+
| Semantic model overview | https://strata.do/developer-docs/developer-guide/semantic-model |
|
|
486
|
+
| Core concepts | https://strata.do/developer-docs/developer-guide/getting-started/concepts |
|
|
487
|
+
| Glossary | https://strata.do/developer-docs/developer-guide/getting-started/glossary |
|
|
488
|
+
| Tables | https://strata.do/developer-docs/developer-guide/semantic-model/tables |
|
|
489
|
+
| Fields | https://strata.do/developer-docs/developer-guide/semantic-model/fields |
|
|
490
|
+
| Expressions (SQL) | https://strata.do/developer-docs/developer-guide/semantic-model/expressions/sql |
|
|
491
|
+
| Relationships | https://strata.do/developer-docs/developer-guide/semantic-model/relationships/cardinality |
|
|
492
|
+
| Imports | https://strata.do/developer-docs/developer-guide/semantic-model/imports |
|
|
493
|
+
| Compound measures | https://strata.do/developer-docs/developer-guide/semantic-model/fields/measures/compound |
|
|
494
|
+
| Snapshot measures | https://strata.do/developer-docs/developer-guide/semantic-model/fields/measures/snapshot |
|
|
495
|
+
| Inclusions | https://strata.do/developer-docs/developer-guide/advanced/inclusions |
|
|
496
|
+
| Exclusions | https://strata.do/developer-docs/developer-guide/advanced/exclusions |
|
|
497
|
+
| Partitions | https://strata.do/developer-docs/developer-guide/advanced/partitions |
|
|
498
|
+
| Cost optimization | https://strata.do/developer-docs/developer-guide/advanced/cost-optimization |
|
|
499
|
+
| Extended blending | https://strata.do/developer-docs/developer-guide/semantic-model/expressions/extended-blending |
|
|
500
|
+
| CLI audit | https://strata.do/developer-docs/developer-guide/cli/audit |
|
|
501
|
+
| CLI deployment | https://strata.do/developer-docs/developer-guide/cli/deployment |
|
|
502
|
+
| CLI migrations | https://strata.do/developer-docs/developer-guide/cli/migrations |
|
|
503
|
+
| Star schema example | https://strata.do/developer-docs/developer-guide/examples/patterns/star-schema |
|
|
504
|
+
| TPC-DS tutorial | https://strata.do/developer-docs/developer-guide/examples/tpcds-tutorial |
|
|
505
|
+
| AI agents and Strata | https://strata.do/developer-docs/developer-guide/api/ai-agents |
|
|
@@ -70,8 +70,13 @@ fields:
|
|
|
70
70
|
# # Optional: UI rendering of the field
|
|
71
71
|
# display_type: default|html|url|email|phone_number|image
|
|
72
72
|
#
|
|
73
|
-
# # Optional:
|
|
74
|
-
#
|
|
73
|
+
# # Optional: value formatting (shortcut string or hash with type)
|
|
74
|
+
# format: currency:2
|
|
75
|
+
# # format:
|
|
76
|
+
# # type: percent
|
|
77
|
+
# # precision: 1
|
|
78
|
+
# # Types: number, currency, percent, date, datetime, html, javascript
|
|
79
|
+
# # Shortcuts: number:2, number:2:abbreviate, currency:2, percent:1, date:short, datetime:iso
|
|
75
80
|
#
|
|
76
81
|
# # Optional: disable listing individual elements (dimension only). Good to do that for
|
|
77
82
|
# # high cardinality columns like account_id
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../agent_output"
|
|
4
|
+
|
|
3
5
|
module Strata
|
|
4
6
|
module CLI
|
|
5
7
|
module DatasourceHelper
|
|
@@ -24,6 +26,19 @@ module Strata
|
|
|
24
26
|
# 3. Check available datasources
|
|
25
27
|
ds_keys = datasources.keys
|
|
26
28
|
|
|
29
|
+
if agent_mode?
|
|
30
|
+
if ds_keys.empty?
|
|
31
|
+
agent_emit_no_datasources!
|
|
32
|
+
elsif ds_keys.length == 1
|
|
33
|
+
return ds_keys.first
|
|
34
|
+
else
|
|
35
|
+
AgentOutput.emit_error(
|
|
36
|
+
"Datasource key required. Available: #{ds_keys.join(", ")}",
|
|
37
|
+
code: "datasource_key_required"
|
|
38
|
+
)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
27
42
|
if ds_keys.empty?
|
|
28
43
|
say "No datasources configured. Run 'strata datasource add' first.", :red
|
|
29
44
|
nil
|
|
@@ -134,6 +149,17 @@ module Strata
|
|
|
134
149
|
|
|
135
150
|
private
|
|
136
151
|
|
|
152
|
+
def agent_emit_no_datasources!
|
|
153
|
+
AgentOutput.emit_error(
|
|
154
|
+
"No datasources configured in datasources.yml. Ask the user to add one interactively ā " \
|
|
155
|
+
"do not create datasources or write credentials yourself (secrets belong in .strata via CLI prompts). " \
|
|
156
|
+
"User command: strata datasource add [ADAPTER] (e.g. strata datasource add postgres, strata datasource add duckdb).",
|
|
157
|
+
code: "no_datasources",
|
|
158
|
+
user_action_required: true,
|
|
159
|
+
suggested_command: "strata datasource add [ADAPTER]"
|
|
160
|
+
)
|
|
161
|
+
end
|
|
162
|
+
|
|
137
163
|
def validate_datasource(ds_key)
|
|
138
164
|
unless datasources[ds_key]
|
|
139
165
|
say "Error: Datasource '#{ds_key}' not found in datasources.yml", :red
|
data/lib/strata/cli/main.rb
CHANGED
|
@@ -9,6 +9,7 @@ require_relative "sub_commands/table"
|
|
|
9
9
|
require_relative "sub_commands/create"
|
|
10
10
|
require_relative "sub_commands/audit"
|
|
11
11
|
require_relative "helpers/description_helper"
|
|
12
|
+
require_relative "agent_mode"
|
|
12
13
|
require_relative "output"
|
|
13
14
|
|
|
14
15
|
module Strata
|
|
@@ -16,6 +17,7 @@ module Strata
|
|
|
16
17
|
class Main < Thor
|
|
17
18
|
include Guard
|
|
18
19
|
include Output
|
|
20
|
+
include AgentMode
|
|
19
21
|
extend Helpers::DescriptionHelper
|
|
20
22
|
|
|
21
23
|
def self.exit_on_failure?
|
|
@@ -60,11 +62,6 @@ module Strata
|
|
|
60
62
|
desc "table", "Manage semantic tables"
|
|
61
63
|
subcommand "table", SubCommands::Table
|
|
62
64
|
|
|
63
|
-
desc "tables", "List all semantic models in the project"
|
|
64
|
-
def tables
|
|
65
|
-
SubCommands::Table.start(["list"])
|
|
66
|
-
end
|
|
67
|
-
|
|
68
65
|
desc "audit", "Audit project configuration and models"
|
|
69
66
|
subcommand "audit", SubCommands::Audit
|
|
70
67
|
|
|
@@ -5,6 +5,7 @@ require_relative "../terminal"
|
|
|
5
5
|
require_relative "../credentials"
|
|
6
6
|
require_relative "../output"
|
|
7
7
|
require_relative "../helpers/datasource_helper"
|
|
8
|
+
require_relative "../agent_mode"
|
|
8
9
|
require_relative "../utils/yaml_import_resolver"
|
|
9
10
|
require_relative "../utils/import_manager"
|
|
10
11
|
require "yaml"
|
|
@@ -19,11 +20,20 @@ module Strata
|
|
|
19
20
|
include Terminal
|
|
20
21
|
include Output
|
|
21
22
|
include DatasourceHelper
|
|
23
|
+
include AgentMode
|
|
22
24
|
|
|
23
25
|
REQUIRED_KEYS_FOR_TABLE_MODEL = %w[name physical_name fields datasource].freeze
|
|
24
26
|
REQUIRED_KEYS_FOR_RELATIONSHIP_MODEL = ["datasource"].freeze
|
|
25
27
|
REQUIRED_KEYS_FOR_RELATIONSHIP_DEFINITION = %w[left right sql cardinality].freeze
|
|
26
28
|
RELATIONSHIP_CARDINALITIES = %w[one_to_one one_to_many many_to_one many_to_many].freeze
|
|
29
|
+
VALID_FORMAT_TYPES = %w[raw number currency percent date datetime html javascript].freeze
|
|
30
|
+
ALLOWED_FIELD_KEYS = %w[
|
|
31
|
+
type name description hidden grains data_type display_type format
|
|
32
|
+
secure disable_listing value_list_size snapshot exclusion_type
|
|
33
|
+
exclusions inclusions extended_blend_group synonyms expression
|
|
34
|
+
].freeze
|
|
35
|
+
ALLOWED_EXPRESSION_KEYS = %w[sql array lookup primary_key].freeze
|
|
36
|
+
EXPRESSION_BOOLEAN_KEYS = %w[array lookup primary_key].freeze
|
|
27
37
|
|
|
28
38
|
# Set default command so `strata audit` still works as `strata audit all`
|
|
29
39
|
default_command :all
|
|
@@ -99,8 +109,8 @@ module Strata
|
|
|
99
109
|
def audit_models
|
|
100
110
|
failures = []
|
|
101
111
|
Dir.glob("models/**/*.yml").each do |file|
|
|
102
|
-
audit_model_file(file, failures)
|
|
103
112
|
audit_imports(file, failures)
|
|
113
|
+
audit_model_file(file, failures)
|
|
104
114
|
end
|
|
105
115
|
failures
|
|
106
116
|
end
|
|
@@ -109,17 +119,21 @@ module Strata
|
|
|
109
119
|
content = YAML.safe_load_file(file, permitted_classes: [Date, Time], aliases: true) || {}
|
|
110
120
|
return unless content.is_a?(Hash)
|
|
111
121
|
|
|
112
|
-
validate_structure(content, file, failures)
|
|
113
|
-
|
|
114
122
|
case model_type(file)
|
|
115
123
|
when :table
|
|
116
|
-
|
|
124
|
+
resolved = Utils::YamlImportResolver.resolve(file, Dir.pwd)
|
|
125
|
+
return unless resolved.is_a?(Hash)
|
|
126
|
+
|
|
127
|
+
validate_structure(resolved, file, failures)
|
|
128
|
+
validate_table_model(resolved, file, failures)
|
|
117
129
|
when :relationship
|
|
130
|
+
validate_structure(content, file, failures)
|
|
118
131
|
validate_relationship_model(content, file, failures)
|
|
119
132
|
end
|
|
120
|
-
rescue
|
|
121
|
-
|
|
122
|
-
|
|
133
|
+
rescue Strata::ImportError => e
|
|
134
|
+
failures << {file: file, message: e.message}
|
|
135
|
+
rescue Psych::SyntaxError
|
|
136
|
+
# Handled by audit_yaml_syntax when run
|
|
123
137
|
end
|
|
124
138
|
|
|
125
139
|
def model_type(file)
|
|
@@ -140,7 +154,7 @@ module Strata
|
|
|
140
154
|
def validate_table_model(content, file, failures)
|
|
141
155
|
validate_required_keys(content, file, REQUIRED_KEYS_FOR_TABLE_MODEL, failures)
|
|
142
156
|
validate_datasource_reference(content, file, failures)
|
|
143
|
-
validate_table_fields(content["fields"], file, failures)
|
|
157
|
+
validate_table_fields(content["fields"], file, failures)
|
|
144
158
|
end
|
|
145
159
|
|
|
146
160
|
def validate_relationship_model(content, file, failures)
|
|
@@ -172,10 +186,119 @@ module Strata
|
|
|
172
186
|
return
|
|
173
187
|
end
|
|
174
188
|
|
|
189
|
+
if fields.empty?
|
|
190
|
+
failures << {file: file, message: "'fields' must not be empty"}
|
|
191
|
+
return
|
|
192
|
+
end
|
|
193
|
+
|
|
175
194
|
fields.each_with_index do |field, idx|
|
|
176
195
|
unless field.is_a?(Hash) && field.key?("name")
|
|
177
196
|
failures << {file: file, message: "Field at index #{idx} missing 'name'"}
|
|
197
|
+
next
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
validate_field_definition(field, file, idx, failures)
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def validate_field_definition(field, file, idx, failures)
|
|
205
|
+
field_name = field["name"] || "index #{idx}"
|
|
206
|
+
|
|
207
|
+
unknown_keys = field.keys.map(&:to_s) - ALLOWED_FIELD_KEYS
|
|
208
|
+
if unknown_keys.any?
|
|
209
|
+
failures << {
|
|
210
|
+
file: file,
|
|
211
|
+
message: "Field '#{field_name}' has unknown #{(unknown_keys.size == 1) ? "key" : "keys"}: #{unknown_keys.sort.join(", ")}"
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
validate_field_expression(field_name, field["expression"], file, failures)
|
|
216
|
+
|
|
217
|
+
return unless field.key?("format")
|
|
218
|
+
|
|
219
|
+
validate_field_format(field_name, field["format"], file, failures)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def validate_field_expression(field_name, expression, file, failures)
|
|
223
|
+
if expression.nil?
|
|
224
|
+
failures << {file: file, message: "Field '#{field_name}' missing required 'expression'"}
|
|
225
|
+
return
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
if expression.is_a?(String)
|
|
229
|
+
if expression.strip.empty?
|
|
230
|
+
failures << {file: file, message: "Field '#{field_name}' expression must not be empty"}
|
|
231
|
+
end
|
|
232
|
+
return
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
unless expression.is_a?(Hash)
|
|
236
|
+
type_name = expression.class.name
|
|
237
|
+
failures << {
|
|
238
|
+
file: file,
|
|
239
|
+
message: "Field '#{field_name}' expression must be a string shortcut or a mapping (got #{type_name})"
|
|
240
|
+
}
|
|
241
|
+
return
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
unknown_keys = expression.keys.map(&:to_s) - ALLOWED_EXPRESSION_KEYS
|
|
245
|
+
if unknown_keys.any?
|
|
246
|
+
failures << {
|
|
247
|
+
file: file,
|
|
248
|
+
message: "Field '#{field_name}' expression has unknown #{(unknown_keys.size == 1) ? "key" : "keys"}: " \
|
|
249
|
+
"#{unknown_keys.sort.join(", ")}"
|
|
250
|
+
}
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
sql = expression["sql"] || expression[:sql]
|
|
254
|
+
if sql.nil? || sql.to_s.strip.empty?
|
|
255
|
+
failures << {file: file, message: "Field '#{field_name}' expression must include non-empty 'sql'"}
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
EXPRESSION_BOOLEAN_KEYS.each do |key|
|
|
259
|
+
next unless expression.key?(key) || expression.key?(key.to_sym)
|
|
260
|
+
|
|
261
|
+
value = expression[key] || expression[key.to_sym]
|
|
262
|
+
next if value == true || value == false
|
|
263
|
+
|
|
264
|
+
failures << {
|
|
265
|
+
file: file,
|
|
266
|
+
message: "Field '#{field_name}' expression.#{key} must be true or false"
|
|
267
|
+
}
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def validate_field_format(field_name, format_value, file, failures)
|
|
272
|
+
case format_value
|
|
273
|
+
when String
|
|
274
|
+
shortcut = format_value.strip
|
|
275
|
+
if shortcut.empty?
|
|
276
|
+
failures << {file: file, message: "Field '#{field_name}' format must not be empty"}
|
|
277
|
+
return
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
type = shortcut.split(":", 2).first
|
|
281
|
+
unless VALID_FORMAT_TYPES.include?(type)
|
|
282
|
+
failures << {
|
|
283
|
+
file: file,
|
|
284
|
+
message: "Field '#{field_name}' format shortcut has invalid type '#{type}' (expected one of: #{VALID_FORMAT_TYPES.join(", ")})"
|
|
285
|
+
}
|
|
286
|
+
end
|
|
287
|
+
when Hash
|
|
288
|
+
type = format_value["type"] || format_value[:type]
|
|
289
|
+
if type.nil? || type.to_s.strip.empty?
|
|
290
|
+
failures << {file: file, message: "Field '#{field_name}' format hash must include type"}
|
|
291
|
+
elsif !VALID_FORMAT_TYPES.include?(type.to_s)
|
|
292
|
+
failures << {
|
|
293
|
+
file: file,
|
|
294
|
+
message: "Field '#{field_name}' format hash has invalid type '#{type}' (expected one of: #{VALID_FORMAT_TYPES.join(", ")})"
|
|
295
|
+
}
|
|
178
296
|
end
|
|
297
|
+
else
|
|
298
|
+
failures << {
|
|
299
|
+
file: file,
|
|
300
|
+
message: "Field '#{field_name}' format must be a string shortcut or a hash with type"
|
|
301
|
+
}
|
|
179
302
|
end
|
|
180
303
|
end
|
|
181
304
|
|
|
@@ -254,13 +377,9 @@ module Strata
|
|
|
254
377
|
imported_content = YAML.safe_load_file(resolved_path, permitted_classes: [Date, Time], aliases: true) || {}
|
|
255
378
|
|
|
256
379
|
unless imported_content.is_a?(Hash)
|
|
257
|
-
failures << {file: file, message: "Imported file '#{import_path}' does not contain valid YAML
|
|
380
|
+
failures << {file: file, message: "Imported file '#{import_path}' does not contain valid YAML"}
|
|
258
381
|
next
|
|
259
382
|
end
|
|
260
|
-
|
|
261
|
-
if model_type(file) == :table && imported_content["fields"]
|
|
262
|
-
validate_table_fields(imported_content["fields"], file, failures)
|
|
263
|
-
end
|
|
264
383
|
rescue Strata::InvalidImportPathError => e
|
|
265
384
|
failures << {file: file, message: e.message}
|
|
266
385
|
rescue Strata::MissingImportError
|
|
@@ -6,6 +6,7 @@ require_relative "../api/client"
|
|
|
6
6
|
require_relative "../utils/git"
|
|
7
7
|
require "time"
|
|
8
8
|
require "tty-prompt"
|
|
9
|
+
require_relative "../agent_mode"
|
|
9
10
|
|
|
10
11
|
module Strata
|
|
11
12
|
module CLI
|
|
@@ -13,6 +14,7 @@ module Strata
|
|
|
13
14
|
class Branch < Thor
|
|
14
15
|
include Guard
|
|
15
16
|
include Output
|
|
17
|
+
include AgentMode
|
|
16
18
|
|
|
17
19
|
default_command :list
|
|
18
20
|
class_option :environment, aliases: ["e"], type: :string
|
|
@@ -10,6 +10,7 @@ require_relative "../ai/services/table_generator"
|
|
|
10
10
|
require_relative "../helpers/prompts"
|
|
11
11
|
require_relative "../helpers/description_helper"
|
|
12
12
|
require_relative "../helpers/command_context"
|
|
13
|
+
require_relative "../agent_mode"
|
|
13
14
|
require "tty-prompt"
|
|
14
15
|
require "tty-spinner"
|
|
15
16
|
require "yaml"
|
|
@@ -25,6 +26,7 @@ module Strata
|
|
|
25
26
|
include Prompts
|
|
26
27
|
include Thor::Actions
|
|
27
28
|
include Helpers::CommandContext
|
|
29
|
+
include AgentMode
|
|
28
30
|
extend Helpers::DescriptionHelper
|
|
29
31
|
|
|
30
32
|
desc "table TABLE_PATH", "Create a semantic table model"
|
|
@@ -34,6 +36,11 @@ module Strata
|
|
|
34
36
|
desc: "Datasource key from datasources.yml"
|
|
35
37
|
|
|
36
38
|
def table(table_path = nil)
|
|
39
|
+
reject_agent_mode!(
|
|
40
|
+
"Agent mode: write models/tbl.*.yml directly. Use: strata datasource meta DS_KEY TABLE_NAME --agent",
|
|
41
|
+
code: "use_yaml_files"
|
|
42
|
+
)
|
|
43
|
+
|
|
37
44
|
return unless datasource_key
|
|
38
45
|
|
|
39
46
|
# If table_path provided, skip search for speed
|
|
@@ -51,6 +58,11 @@ module Strata
|
|
|
51
58
|
desc: "Datasource key from datasources.yml"
|
|
52
59
|
|
|
53
60
|
def relation(relation_path)
|
|
61
|
+
reject_agent_mode!(
|
|
62
|
+
"Agent mode: write models/rel.*.yml directly.",
|
|
63
|
+
code: "use_yaml_files"
|
|
64
|
+
)
|
|
65
|
+
|
|
54
66
|
return unless datasource_key
|
|
55
67
|
|
|
56
68
|
create_relation_file(relation_path)
|
|
@@ -8,6 +8,7 @@ require_relative "../utils/git"
|
|
|
8
8
|
require "tty-prompt"
|
|
9
9
|
require_relative "../helpers/datasource_helper"
|
|
10
10
|
require_relative "../helpers/description_helper"
|
|
11
|
+
require_relative "../agent_mode"
|
|
11
12
|
|
|
12
13
|
module Strata
|
|
13
14
|
module CLI
|
|
@@ -18,6 +19,7 @@ module Strata
|
|
|
18
19
|
include Terminal
|
|
19
20
|
include Output
|
|
20
21
|
include DatasourceHelper
|
|
22
|
+
include AgentMode
|
|
21
23
|
extend Helpers::DescriptionHelper
|
|
22
24
|
|
|
23
25
|
desc "adapters", "Lists supported data warehouse adapters"
|
|
@@ -52,7 +54,7 @@ module Strata
|
|
|
52
54
|
return
|
|
53
55
|
end
|
|
54
56
|
|
|
55
|
-
names = ds.keys.map { "#{it}
|
|
57
|
+
names = ds.keys.map { "Key: #{it} | Name: #{ds[it]["name"]}" }
|
|
56
58
|
out = "\n #{names.join("\n ")}"
|
|
57
59
|
say out, :magenta
|
|
58
60
|
end
|
|
@@ -184,11 +186,17 @@ module Strata
|
|
|
184
186
|
ds_key = resolve_datasource(ds_key, prompt: prompt)
|
|
185
187
|
return unless ds_key
|
|
186
188
|
|
|
187
|
-
say "\nListing #{ds_key} tables...\n\n", :yellow
|
|
188
189
|
adapter = create_adapter(ds_key)
|
|
189
190
|
tables = adapter.tables(**options)
|
|
190
191
|
tables = tables.select { it =~ /#{options[:pattern]}/ } if options[:pattern]
|
|
191
192
|
|
|
193
|
+
if agent_mode?
|
|
194
|
+
AgentOutput.emit_json({datasource: ds_key, tables: tables})
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
say "\nListing #{ds_key} tables...\n\n", :yellow
|
|
199
|
+
|
|
192
200
|
if tables.empty?
|
|
193
201
|
say "No tables found.", :yellow
|
|
194
202
|
return
|
|
@@ -205,10 +213,18 @@ module Strata
|
|
|
205
213
|
method_option :catalog, aliases: "c", type: :string, desc: "Change the catalog from the configured one."
|
|
206
214
|
method_option :schema, aliases: "s", type: :string, desc: "Change the schema from the configured one."
|
|
207
215
|
def meta(ds_key, table_name)
|
|
208
|
-
say "\nā Schema for table: #{table_name} (#{ds_key}):\n", :yellow
|
|
209
216
|
adapter = create_adapter(ds_key)
|
|
210
217
|
md = adapter.metadata(table_name, **options.transform_keys(&:to_sym))
|
|
211
218
|
|
|
219
|
+
if agent_mode?
|
|
220
|
+
columns = md.columns.map do |column|
|
|
221
|
+
column.to_h.merge(normalized_data_type: column.normalized_data_type)
|
|
222
|
+
end
|
|
223
|
+
AgentOutput.emit_json({datasource: ds_key, table: table_name, columns: columns})
|
|
224
|
+
return
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
say "\nā Schema for table: #{table_name} (#{ds_key}):\n", :yellow
|
|
212
228
|
headings = md.columns.first.to_h.keys
|
|
213
229
|
rows = md.columns.map(&:to_h).map(&:values)
|
|
214
230
|
|
|
@@ -5,6 +5,7 @@ require_relative "../terminal"
|
|
|
5
5
|
require_relative "../output"
|
|
6
6
|
require_relative "../helpers/project_helper"
|
|
7
7
|
require_relative "../helpers/description_helper"
|
|
8
|
+
require_relative "../agent_mode"
|
|
8
9
|
require_relative "../api/client"
|
|
9
10
|
require_relative "../credentials"
|
|
10
11
|
require_relative "../utils"
|
|
@@ -23,6 +24,7 @@ module Strata
|
|
|
23
24
|
include Guard
|
|
24
25
|
include Terminal
|
|
25
26
|
include Output
|
|
27
|
+
include AgentMode
|
|
26
28
|
extend Helpers::DescriptionHelper
|
|
27
29
|
|
|
28
30
|
default_command :deploy
|
|
@@ -4,6 +4,7 @@ require_relative "../guard"
|
|
|
4
4
|
require_relative "../terminal"
|
|
5
5
|
require_relative "../output"
|
|
6
6
|
require_relative "../helpers/project_helper"
|
|
7
|
+
require_relative "../agent_mode"
|
|
7
8
|
require_relative "../api/client"
|
|
8
9
|
require "yaml"
|
|
9
10
|
|
|
@@ -14,6 +15,7 @@ module Strata
|
|
|
14
15
|
include Guard
|
|
15
16
|
include Terminal
|
|
16
17
|
include Output
|
|
18
|
+
include AgentMode
|
|
17
19
|
|
|
18
20
|
desc "link PROJECT_ID", "Link local project to an existing project on the server"
|
|
19
21
|
def link(project_id)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../helpers/prompts"
|
|
4
4
|
require_relative "../helpers/command_context"
|
|
5
|
+
require_relative "../agent_mode"
|
|
5
6
|
require_relative "../output"
|
|
6
7
|
|
|
7
8
|
module Strata
|
|
@@ -14,6 +15,7 @@ module Strata
|
|
|
14
15
|
include Prompts
|
|
15
16
|
include Thor::Actions
|
|
16
17
|
include Helpers::CommandContext
|
|
18
|
+
include AgentMode
|
|
17
19
|
extend Helpers::DescriptionHelper
|
|
18
20
|
|
|
19
21
|
desc "list", "List all semantic models in the project"
|
data/lib/strata/cli/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: strata-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ajo Abraham
|
|
@@ -242,6 +242,8 @@ files:
|
|
|
242
242
|
- Rakefile
|
|
243
243
|
- exe/strata
|
|
244
244
|
- lib/strata/cli.rb
|
|
245
|
+
- lib/strata/cli/agent_mode.rb
|
|
246
|
+
- lib/strata/cli/agent_output.rb
|
|
245
247
|
- lib/strata/cli/ai/client.rb
|
|
246
248
|
- lib/strata/cli/ai/configuration.rb
|
|
247
249
|
- lib/strata/cli/ai/services/table_generator.rb
|