@malloy-publisher/skills 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.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/skills/malloy/SKILL.md +3 -2
  3. package/skills/malloy-analysis/SKILL.md +1 -1
  4. package/skills/malloy-analysis-pitfalls/SKILL.md +1 -1
  5. package/skills/malloy-analysis-report/SKILL.md +2 -2
  6. package/skills/malloy-analyze/SKILL.md +5 -3
  7. package/skills/malloy-charts/SKILL.md +1 -1
  8. package/skills/malloy-dashboards/SKILL.md +123 -59
  9. package/skills/malloy-debug/SKILL.md +1 -1
  10. package/skills/malloy-define/SKILL.md +2 -2
  11. package/skills/malloy-discover/SKILL.md +1 -1
  12. package/skills/malloy-document/SKILL.md +6 -4
  13. package/skills/malloy-getting-started/SKILL.md +1 -1
  14. package/skills/malloy-gotchas-modeling/SKILL.md +1 -1
  15. package/skills/malloy-gotchas-queries/SKILL.md +1 -1
  16. package/skills/malloy-gotchas-rendering/SKILL.md +1 -1
  17. package/skills/malloy-html-data-app-embedding/SKILL.md +1 -1
  18. package/skills/malloy-html-data-app-runtime/SKILL.md +1 -1
  19. package/skills/malloy-html-data-apps/SKILL.md +1 -1
  20. package/skills/malloy-lookml-review/SKILL.md +1 -1
  21. package/skills/malloy-materialization/SKILL.md +1 -1
  22. package/skills/malloy-materialization-tuning/SKILL.md +1 -1
  23. package/skills/malloy-model/SKILL.md +14 -2
  24. package/skills/malloy-model-as-you-go/SKILL.md +306 -0
  25. package/skills/malloy-modeling/SKILL.md +5 -6
  26. package/skills/malloy-notebook-chat/SKILL.md +1 -1
  27. package/skills/malloy-notebooks/SKILL.md +1 -1
  28. package/skills/malloy-patterns/SKILL.md +1 -1
  29. package/skills/malloy-phrase-detection/SKILL.md +1 -1
  30. package/skills/malloy-publish/SKILL.md +1 -1
  31. package/skills/malloy-queries/SKILL.md +1 -1
  32. package/skills/malloy-review/SKILL.md +1 -1
  33. package/skills/malloy-scope/SKILL.md +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@malloy-publisher/skills",
3
3
  "description": "Agent skills for Malloy and the Malloy Publisher",
4
- "version": "0.1.9",
4
+ "version": "0.1.10",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy
3
3
  description: Index of all Malloy skills. Use when user asks "malloy help", "what malloy skills are available", "how do I use malloy", or needs guidance on which Malloy skill to use.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -42,7 +42,8 @@ Every skill in this deployment, by what it is for. Start at a driver; it routes
42
42
 
43
43
  | Skill | Use when... |
44
44
  |-------|-------------|
45
- | `skill:malloy-analyze` | Exploratory data analysis: profiling, building views and dashboards |
45
+ | `skill:malloy-model-as-you-go` | After answering a question, writing down what it assumed: a `#(doc)`'d field in the model, an `extend` in the notebook, or a stated assumption plus snippet, depending on what the session can write |
46
+ | `skill:malloy-analyze` | Open-ended exploration with no intent to keep anything: profiling, hypotheses, views |
46
47
  | `skill:malloy-charts` | Chart selection and renderer reference for Malloy visualizations |
47
48
  | `skill:malloy-notebooks` | Building Malloy notebooks (.malloynb) |
48
49
  | `skill:malloy-analysis-report` | Combining validated queries into a notebook report or dashboard |
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-analysis
3
3
  description: Workflow for answering data questions against Malloy semantic models over MCP - structured discovery with get_context, query construction with execute_query, verification, and answer delivery. Use whenever the user asks a data question, wants a metric, a breakdown, a trend, or a chart over a model.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-analysis-pitfalls
3
3
  description: Common data analysis pitfalls to watch for during query construction and result interpretation. Reference this checklist when verifying queries and results to catch errors before presenting an answer.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-analysis-report
3
3
  description: Combine validated Malloy queries into a notebook report or dashboard. Use when the user asks to "create a report", "build a dashboard", "combine these into a report", or wants a persistent multi-query artifact.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -24,7 +24,7 @@ Do NOT build the notebook in the same turn as `execute_query`. Explain first, th
24
24
 
25
25
  ## Filters are inherited from the model, don't declare them in the report
26
26
 
27
- Reports do not (and cannot) define their own filters. If the source has `#(filter)` annotations, Publisher renders the filter widgets, parses caller parameters, and injects `where:` clauses server-side automatically: the report inherits and displays those filters with no extra work. If the analysis needs a knob the source doesn't expose, the right move is to add a `#(filter)` to the source itself (see your modeling workflow's parameterizable-filter guidance for `#(filter)`), not to wedge a filter widget into the report. For curated notebooks with their own per-notebook filter UI on top of the model, see `skill:malloy-notebooks` instead.
27
+ Reports do not (and cannot) define their own filters. If the source declares `given:` parameters (or legacy `#(filter)` annotations), Publisher renders the controls, parses caller parameters, and applies them server-side automatically: the report inherits and displays them with no extra work. If the analysis needs a knob the source doesn't expose, the right move is to add a `given:` to the source itself, not to wedge a filter widget into the report. `#(filter)` is deprecated in favour of native Malloy `given:` parameters. Do not add new `#(filter)` annotations; the two exceptions are `required` and `implicit`, which `given:` cannot cover yet. See `skill:malloy-model` § Legacy: Parameterizable Filters. For curated notebooks with their own per-notebook filter UI on top of the model, see `skill:malloy-notebooks` instead.
28
28
 
29
29
  ## What goes in the report
30
30
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-analyze
3
- description: Explore data for insights and build views/dashboards/notebooks. Use when user asks to "analyze this data", "find insights", "explore for patterns", "what's interesting", "what's driving X", "build a dashboard", "create views", or any analysis task. For EDA exploration, start at Step 1. For building views on an existing model, jump to View Patterns.
4
- version: 0.1.9
3
+ description: Open-ended exploration with no specific question to answer, and building views/dashboards/notebooks on an existing model. Use when the user asks "what's interesting?", "explore this data", "find insights", "look for patterns", "build a dashboard", or "create views". Not for a specific data question - answering one is `malloy-analysis`, and writing down what the answer assumed is `malloy-model-as-you-go`. For exploration start at Step 1; for views on an existing model jump to View Patterns.
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -254,7 +254,9 @@ A notebook is also the home for a polished, narrated report: alternate `>>>markd
254
254
 
255
255
  ### Interactive Filters
256
256
 
257
- **Notebooks do NOT define filters themselves.** When you import a model, the model's `#(filter)` annotations on the source are **inherited and displayed automatically**: the publisher renders the filter widgets, parses caller parameters, and injects `where:` clauses server-side. You don't redeclare them in the consumer. If the analysis needs a knob the model doesn't expose, the right move is to add a `#(filter)` to the source itself (see `skill:malloy-model` § Parameterizable Filters with `#(filter)`), not to wedge filtering into the consumer.
257
+ **Notebooks do NOT define filters themselves.** When you import a model, the model's runtime parameters are **inherited and displayed automatically**: the publisher renders the controls, parses caller parameters, and applies them server-side. You don't redeclare them in the consumer. If the analysis needs a knob the model doesn't expose, the right move is to add it to the source itself, not to wedge filtering into the consumer.
258
+
259
+ **Declare that knob as a `given:`.** `#(filter)` is deprecated in favour of native Malloy `given:` parameters. Do not add new `#(filter)` annotations; the two exceptions are `required` and `implicit`, which `given:` cannot cover yet. See `skill:malloy-model` § Legacy: Parameterizable Filters.
258
260
 
259
261
  The notebook-level `##(filters)` annotation and the dimension-level `#(filter) {"type": "..."}` JSON-blob form are **unsupported legacy syntax**, don't use them. The only supported form is `#(filter) name=... dimension=... type=...` declared above the source.
260
262
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-charts
3
3
  description: Chart selection guidance and renderer reference for Malloy views. Use when choosing visualization types, adding chart annotations, user asks "what chart should I use", "how should I visualize this", or when deciding between bar_chart, line_chart, scatter_chart, etc.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-dashboards
3
3
  description: "Build or modify a Malloy Publisher dashboard, a tagged .malloy file in a package's dashboards/ directory, with auto-rendered filter controls, a grid layout, and # drill click-through. Use when the user asks for a dashboard, a filterable operational view, or drill-through between views, and no code is wanted."
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -31,8 +31,9 @@ Scanned at a glance is a dashboard; read top to bottom is a notebook.
31
31
  `malloy_getContext` if you have it, otherwise the REST model endpoint or the `.malloy` files.
32
32
  Never guess a name. A guessed field in a query fails the whole package load, not just that one
33
33
  dashboard; a guessed tile or suggest source is quieter, and only shows up in the package warnings.
34
- 2. **DECIDE THE FORM** (below): single-query if the page is one filtered result with parts;
35
- composite if the views already exist and the job is choosing which to show together.
34
+ 2. **PICK THE VIEWS TO SHOW.** A dashboard is `## artifact { tiles=[…] }` naming existing views, so
35
+ this is the design step: which views, how wide each sits, what each is called. There is one form,
36
+ so there is no form to choose.
36
37
  3. **DECLARE THE GIVENS** the dashboard will filter by, in the model (usually `givens.malloy`), with
37
38
  their control tags: see "Filter controls" below for the syntax and what each tag renders as. Skip
38
39
  if they already exist, since a given is a model concern and dashboards share them.
@@ -40,7 +41,11 @@ Scanned at a glance is a dashboard; read top to bottom is a notebook.
40
41
  Import every given it filters by, and every source or query any of those givens names in a
41
42
  `suggest`. Both are per-file, and getting the suggest wrong does not error: the control still
42
43
  looks like a picker but has no options, and says so underneath, "Could not load the options for
43
- this control". The package warnings name it too.
44
+ this control". The package warnings name it too. **A `suggest` naming a `query=` needs that
45
+ query's own source imported as well**, because an import is not transitive: the query resolves by
46
+ name, so the file compiles and the package loads, but running the picker fails with
47
+ `Undefined source '<name>'`. The package warnings name this one too, saying which source to
48
+ import. Import the source that suggest query reads, not just the query.
44
49
  5. **COMPILE IT** with `malloy_compile` (or `POST …/models/<path>/compile`), against the source text,
45
50
  before you save, at the path the file will have. **Editing one that already exists needs
46
51
  `"scope": "file"`**, which compiles your source AS that file; the default appends it instead, so
@@ -48,7 +53,7 @@ Scanned at a glance is a dashboard; read top to bottom is a notebook.
48
53
  already-defined errors that reads as broken Malloy rather than a wrong scope. **Editing a shared
49
54
  include wants `"scope": "package"`**, which recompiles every file as saved: `file` only checks the
50
55
  one you are editing, so renaming a source in `_shared.malloy` passes it while breaking every
51
- composite that imports it. A clean compile is not a working dashboard: some tag mistakes surface
56
+ dashboard that imports it. A clean compile is not a working dashboard: some tag mistakes surface
52
57
  at step 6, and some only when you look at the page in step 7. (The third scope, `append`, is the
53
58
  default and is what a not-yet-saved file gets.)
54
59
  6. **SAVE IT, RELOAD, AND READ THE MANIFEST AND THE WARNINGS.** `malloy_reloadPackage`, or
@@ -60,9 +65,63 @@ Scanned at a glance is a dashboard; read top to bottom is a notebook.
60
65
  and its `query` is the name to run in step 7. See "Read the lint" below.
61
66
  7. **OPEN IT AND LOOK.** Not optional; see "What 'done' means".
62
67
 
63
- ## The two forms
68
+ ## The form
64
69
 
65
- **Single-query:** one query whose result is the dashboard. Reach for this by default.
70
+ A dashboard is `## artifact { tiles=[…] }` at model level: named views, each run as its own query, laid
71
+ out by Publisher into the grid `# dashboard { columns=N }` names.
72
+
73
+ ```malloy
74
+ ##! experimental.givens
75
+ ## artifact { title="Storefront overview" tiles=["overview -> kpis", "overview -> revenue_trend", "overview -> revenue_by_state"] } dashboard { columns=12 }
76
+ import { scoped_sales } from './_shared.malloy'
77
+ import { products } from '../storefront.malloy'
78
+ import { CATEGORY, SINCE } from '../givens.malloy'
79
+
80
+ // Layout goes on the VIEW, and a thin re-declaration is the place to put it: the
81
+ // modelled view keeps its chart tag, and this decides how wide it sits here.
82
+ source: overview is scoped_sales extend {
83
+ # colspan=12
84
+ # label="Key figures"
85
+ # big_value
86
+ view: kpis is {
87
+ aggregate:
88
+ # label="Revenue"
89
+ # currency
90
+ total_sales
91
+ # label="Orders"
92
+ order_count
93
+ }
94
+
95
+ # colspan=8
96
+ # break
97
+ # label="Revenue by month"
98
+ view: revenue_trend is sales_by_month
99
+
100
+ # colspan=4
101
+ # label="Revenue by state"
102
+ view: revenue_by_state is sales_by_state
103
+ }
104
+ ```
105
+
106
+ Model-level because there is no query of its own to hang a `#` tag on, and model-level for a second
107
+ reason: tiles run as separate queries, which is the only way a page can span unrelated sources. A
108
+ nest's pipeline starts from its own query's source and there is no way to combine two.
109
+
110
+ Three things the form costs, so you are not surprised by them:
111
+
112
+ - **A tile expression is a string in an annotation, so the compiler never checks it.** Rename a view
113
+ and the file still compiles; the tile fails at package load. Read the lint (step 6).
114
+ - **No per-parent-row grouping.** There is no parent query to repeat a grid over.
115
+ - **Filtering lives in what the tiles name**, not on the page. That is the shared include's job,
116
+ below.
117
+
118
+ ### Also served: `# artifact` on a `query:`
119
+
120
+ One query whose result is the whole page, laid out by `@malloydata/render` from the query's own
121
+ `# dashboard` tag. This is Malloy's rendering feature, the same thing a notebook cell shows, not a
122
+ second way to build a dashboard, and it cannot span sources. Publisher serves it, so you will meet it
123
+ in existing packages, and the layout tags below are shared with it. Do not author a new dashboard
124
+ this way.
66
125
 
67
126
  ```malloy
68
127
  ##! experimental.givens
@@ -108,25 +167,16 @@ query: overview is order_items -> {
108
167
 
109
168
  The `#"` line above the tag is a doc comment, and it is the page's description. If you leave `title=`
110
169
  off the artifact tag, it becomes the title instead, so write it as one, not as a sentence about the
111
- page. **It belongs to the query, so it only works on this form.** Putting a `#"` above a
170
+ page. **It belongs to a query, so it only works on the one-query form.** Putting a `#"` above a
112
171
  model-level `##` tag fails the whole package load with "Object annotation not connected to any
113
- object", and a composite has no description as a result.
172
+ object", so a dashboard has no description as a result.
114
173
 
115
- **Composite:** a list of views that already exist, each run separately into one grid. The tag is
116
- model-level (`##`) because there is no query of its own to hang a `#` tag on.
117
-
118
- ```malloy
119
- ##! experimental.givens
120
- ## artifact { title="Seasonality" tiles=["scoped_sales -> sales_by_month", "scoped_sales -> sales_by_year", "scoped_sales -> seasonality"] dashboard_columns=3 }
121
- import { scoped_sales } from './_shared.malloy'
122
- import { products } from '../storefront.malloy'
123
- import { CATEGORY, SINCE } from '../givens.malloy'
124
- ```
174
+ ### Where the filtering goes
125
175
 
126
- A composite has no query, so the filtering it applies must live in what it composes: a source that
176
+ A dashboard has no query, so the filtering it applies must live in what it composes: a source that
127
177
  already has the givens applied. Put it in an untagged `dashboards/_shared.malloy`, which discovery
128
- treats as a shared include rather than a dashboard. **It has to apply every given the composite
129
- imports**: a given the composite imports but nothing references gets no control, silently, at reload
178
+ treats as a shared include rather than a dashboard. **It has to apply every given the dashboard
179
+ imports**: a given the dashboard imports but nothing references gets no control, silently, at reload
130
180
  200 with no warning. Note `SINCE` is a `date` rather than a `filter<>`, so it compares with `>=`
131
181
  rather than `~`. Save the include before you compile the dashboard that imports it, since an importer
132
182
  compiled against a sibling that is not on disk fails with an `import-error`.
@@ -141,42 +191,56 @@ source: scoped_sales is order_items extend {
141
191
  }
142
192
  ```
143
193
 
144
- Its tiles are equal-width (there is no per-tile colspan), and each one takes a single column, so
145
- **`dashboard_columns` is how many tiles you want per row**, not a number of twelfths. Three tiles want
146
- `dashboard_columns=3`. Setting it to 12 out of habit gives you twelve columns and three tiles a
147
- twelfth of the page wide. Use the single-query form when one tile deserves more room than the others.
194
+ **`# dashboard { columns=N }` is the one spelling of the grid width**, on both forms, beside the
195
+ artifact tag. Anything else inside the artifact tag is a package warning naming it, which is what you
196
+ will see if you write `dashboard_columns=N`: nothing reads it, and the grid would otherwise fall back
197
+ to the default width in silence.
148
198
 
149
- **Write `dashboard_columns=N` on a composite and `# dashboard { columns=N }` on a single query.**
150
- A composite forgives the mix, since both spellings feed the manifest field it
151
- lays out from, but **a single query tagged `dashboard_columns` silently loses its whole layout**, and
152
- the manifest reports the count either way: see "Losing the grid". A composite's tiles also keep their
153
- own field names on their axes and column headers, and the Layout section's remedy, inlining the view,
154
- is not open to you here: naming existing views is the whole point of the form. `tiles=` on a
155
- single-query artifact tag is dropped the same way, silently.
199
+ A tile keeps its view's own field names on axes and column headers. `# label` titles the tile; to label
200
+ what is inside it, label the fields in the view.
156
201
 
157
202
  ## Layout: the four tags that make a page line up
158
203
 
159
- This section is the **single-query** form only. A composite has no colspans and counts tiles per row,
160
- above. Cards and tiles share one grid, so copy this recipe, and use the same count on every
161
- single-query dashboard in the package so they read as one product:
204
+ Cards and tiles share one grid, and the same four tags work on both forms: on a `# dashboard` query
205
+ the renderer reads them off each nest, and on a dashboard's `tiles=[…]` Publisher reads them off the
206
+ view each tile names. Copy this recipe and use the same count on every dashboard in the package so
207
+ they read as one product:
162
208
 
163
209
  1. **`columns=12`** on the `# dashboard` tag. Twelve divides by 2, 3, 4 and 6, so a row is even with
164
210
  three cards or four.
165
211
  2. **A `# colspan` on every card and tile, summing to 12 per row.** Four cards at 3, three at 4, two
166
212
  tiles at 6, a full-width table at 12. Omit them and every item falls to a single column, a twelfth
167
- of the width, which is too narrow for a line chart to draw in at all.
213
+ of the width, which is too narrow for a line chart to draw in at all. A colspan wider than
214
+ `columns` is clamped, and the package warnings say so.
168
215
  3. **`# break` on the first tile after the cards.** Otherwise it flows into the columns left beside
169
216
  the cards and the next tile wraps. Not needed per row: once a row sums to 12 the next item wraps
170
217
  on its own.
171
- 4. **`# label="…"` on every nest and every aggregate**, including the aggregates inside a table
172
- nest, whose column headers are field names too. The heading is otherwise the view's or field's
173
- name, and a wide table full of `total_sales` and `order_item_count` is the most visible thing
174
- between a rough page and a finished one. A view nested **by name** is the exception: you can label
175
- the tile, but its own field names still reach the chart axes and the column headers, so inline it
176
- if you want those labelled too.
218
+ 4. **`# label="..."` on every nest, tile view, and aggregate**, including the aggregates inside a table
219
+ nest, whose column headers are field names too. The heading is otherwise derived from the view's or
220
+ field's name, and a wide table full of `total_sales` and `order_item_count` is the most visible
221
+ thing between a rough page and a finished one. A view referenced **by name** is the exception: you
222
+ can label the tile, but its own field names still reach the chart axes and the column headers, so
223
+ label the fields in the view if you want those too.
224
+
225
+ On a dashboard, all four go on the view a tile names, which is why a thin re-declaration
226
+ (`view: revenue_trend is sales_by_month`) is the place to put them: the modelled view stays reusable
227
+ and each page decides its own widths. `# subtitle="..."` and `# borderless` go there too and are the
228
+ rest of the set; a tile reads all five exactly as a `# dashboard` nest child does.
229
+
230
+ **Do not forget `columns=` itself.** What you get without it differs by form, and neither is the page
231
+ you drew. On a dashboard it falls back to a narrow default and every colspan is clamped to it, which
232
+ is the usual reason a page you laid out comes out one item per row, and the package warnings name it.
233
+ On a `# dashboard` query there is no grid at all: the cards flow side by side at their natural widths
234
+ and every colspan is ignored. No package warning says so, because the renderer reports it at query
235
+ time. See "Losing the grid".
177
236
 
178
237
  Then the traps:
179
238
 
239
+ - **A KPI row is authored differently on the two forms.** On a `# dashboard` query a top-level
240
+ `aggregate:` measure IS the card, and nesting a `# big_value` view to get one renders it embedded,
241
+ as full-width bars in a single tile. A dashboard has no top-level aggregates, since a tile is one
242
+ whole result, so there a `# big_value` view IS the KPI row and renders as one. This is the only
243
+ place the two forms need different Malloy for the same picture.
180
244
  - **No `# size=fill` on a dashboard tile.** Inside a dashboard it measures against the container the
181
245
  whole grid was handed, not the tile, so it yields a chart thousands of pixels tall. Tiles already
182
246
  size to their colspan.
@@ -196,9 +260,9 @@ The last two are upstream renderer behavior, cheap to work around in the model.
196
260
 
197
261
  The same tags govern a `# dashboard` **view** run in a notebook cell, since both surfaces render
198
262
  through the same code, so a view laid out this way looks the same in a cell as on a dashboard page.
199
- Height is the one thing the surface decides: a single-query dashboard renders at its natural height,
200
- a composite's tiles are each capped, and in a notebook a chart cell is capped and a table cell hugs
201
- its rows.
263
+ Height is the one thing the surface decides: a one-query page renders at its natural height, a
264
+ dashboard's tiles are each capped, and in a notebook a chart cell is capped and a table cell hugs its
265
+ rows.
202
266
 
203
267
  ## The rules that actually bite
204
268
 
@@ -207,7 +271,7 @@ its rows.
207
271
  collides with an imported `regions` source).
208
272
  - **Importing a given is what makes it bindable.** Malloy's given namespace is per-file. A given the
209
273
  dashboard file does not import gets no control and cannot be sent to it, even when the `where:`
210
- that references it lives up an import chain. A composite must import the givens its tiles use.
274
+ that references it lives up an import chain. A dashboard must import the givens its tiles use.
211
275
  - **A suggest's source or query has to resolve in the dashboard file too.** `suggest { source=products … }`
212
276
  means the dashboard imports `products`.
213
277
  - **A model-level `##` tag must be on one line.** Wrapping one always breaks it, but how you find
@@ -318,15 +382,14 @@ either, for a different reason.
318
382
 
319
383
  ## Losing the grid
320
384
 
321
- A single-query dashboard can come out with its layout wrong in two visibly different ways, and the
322
- reload is 200 and the manifest reports the column count you asked for in both.
385
+ A one-query page can come out with its layout wrong in two visibly different ways, and the reload is
386
+ 200 and the manifest reports the column count you asked for in both.
323
387
 
324
- **Not a dashboard at all: one plain nested table**, every `# colspan` and `# break` dropped. Either
325
- you tagged a single query with `dashboard_columns=N`, which only a composite reads, or a `f'…'`
326
- filter literal in a `givens { … }`
327
- block shares a line with `# dashboard`. For the second, put `# dashboard` on its own line: writing it
328
- first on the line does not help, a plain `'Outerwear'` or a bare date is fine, and the composite form
329
- is immune because its layout comes from the manifest rather than a re-parse.
388
+ **Not a dashboard at all: one plain nested table**, every `# colspan` and `# break` dropped, because a
389
+ `f'…'` filter literal in a `givens { … }` block shares a line with `# dashboard`. Put `# dashboard` on
390
+ its own line: writing it first on the line does not help, a plain `'Outerwear'` or a bare date is
391
+ fine, and a `tiles=[…]` dashboard is immune because its layout comes from the manifest rather than a
392
+ re-parse.
330
393
 
331
394
  **A dashboard, but nothing lines up**: you wrote `# colspan` without `columns=N`, so the items flow
332
395
  side by side at their natural widths instead of aligning to a grid.
@@ -355,7 +418,9 @@ Package warnings after a reload are the dashboard's test suite. Fix all of them:
355
418
  - `filters by given "X", which this file does not import, so no control is shown for it`: the trap
356
419
  under "Importing a given is what makes it bindable", which the lint now names for you, with the file
357
420
  to fix.
358
- - A tile that does not resolve to a real view, or a non-positive `dashboard_columns`.
421
+ - A tile that does not resolve to a real view; a non-positive `# dashboard { columns }`; a tile view's
422
+ `# colspan` that is not a positive integer or is wider than the grid; and any property inside the
423
+ artifact tag Publisher does not read, `dashboard_columns=` included.
359
424
 
360
425
  Findings carry a `severity`, but `warn` is the ordinary default and tells you nothing about how bad
361
426
  one is. Read the text, not the severity and not the count. One message is worth recognising because
@@ -401,9 +466,8 @@ the warning list.
401
466
  - Each control renders as the widget you intended (a select shows options; a slider is a slider),
402
467
  and changing one changes the numbers.
403
468
  - If you added a `# drill`, you clicked it and landed where you meant to, with the given seeded.
404
- - On a single-query dashboard, every card and tile carries a colspan, each row's colspans sum to
405
- `columns`, and the rows end flush with each other. On a composite, the tiles fill the row rather
406
- than leaving a gap. Nothing is clipped, no tile is thousands of pixels tall, and no legend or card
469
+ - Every card and tile carries a colspan, each row's colspans sum to `columns`, and the rows end flush
470
+ with each other. Nothing is clipped, no tile is thousands of pixels tall, and no legend or card
407
471
  label ends in `…`.
408
472
 
409
473
  ## Reference
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-debug
3
3
  description: Fix Malloy compile errors and understand error messages. Use when encountering errors in .malloy files, user says "fix this error", "malloy error", "compile error", "syntax error", or sees 20+ cascading errors.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-define
3
3
  description: Propose a source plan and field definitions for a Malloy semantic model. Covers picking which sources to model and at what grain, then proposing the specific renames, dimensions, and measures per source, every proposal backed by querying the data.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -160,7 +160,7 @@ The user will:
160
160
  - **Remove** fields they don't need.
161
161
  - **Change** priorities.
162
162
 
163
- Once the definitions are confirmed, write them into the `.malloy` model (see your modeling workflow). Use `#(doc)` annotations to document sources and fields, and `#(filter)` annotations to declare server-side filterable dimensions where appropriate. Keep the confirmed definitions in the conversation; there is no separate plan-file store.
163
+ Once the definitions are confirmed, write them into the `.malloy` model (see your modeling workflow). Use `#(doc)` annotations to document sources and fields, and `given:` parameters to declare runtime-filterable dimensions where appropriate. `#(filter)` is deprecated in favour of native Malloy `given:` parameters. Do not add new `#(filter)` annotations; the two exceptions are `required` and `implicit`, which `given:` cannot cover yet. See `skill:malloy-model` § Legacy: Parameterizable Filters. Keep the confirmed definitions in the conversation; there is no separate plan-file store.
164
164
 
165
165
  ## Data-driven proposals
166
166
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-discover
3
3
  description: Silent data discovery for Malloy modeling. Used at Step 1 of the modeling workflow. Scans tables, columns, distributions, and relationships without user interaction. The agent builds an internal picture before presenting anything.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-document
3
3
  description: 'Add documentation with #(doc) tags to Malloy models so fields and sources are described in plain language. Use when user asks to "add documentation", "add doc tags", "document the model", or wants fields and sources described for natural-language search and discovery. For declaring parameterizable filters with #(filter), see the malloy-model skill. Filters are a runtime/modeling construct (governance, latency, correctness), not a documentation tag.'
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -15,7 +15,7 @@ Add `#(doc)` tags to describe sources and fields in plain language so they are e
15
15
  | Tag | Purpose | Goes on |
16
16
  |-----|---------|---------|
17
17
  | `#(doc)` | Plain-language description for natural-language search | source, dimension, measure, view, join |
18
- | `#(filter)` | Declare a parameterizable filter (runtime/modeling concern, see `malloy-model`) | source |
18
+ | `#(filter)` | Deprecated, prefer `given:`. Parameterizable filter (runtime/modeling concern, see `malloy-model`) | source |
19
19
 
20
20
  `#(doc)` is a standard Malloy annotation. It documents a field or source with a human-readable description that downstream tools can surface and search against.
21
21
 
@@ -76,9 +76,11 @@ These are governed models: a threshold nobody confirmed is an assumption, and an
76
76
 
77
77
  Do not hedge measured facts: `avg_energy is avg(energy)` needs no caveat. Hedge only where a domain expert could reasonably choose differently.
78
78
 
79
- ## #(filter): see `malloy-model`
79
+ ## #(filter): deprecated, see `malloy-model`
80
80
 
81
- `#(filter)` is also a `#(...)`-shaped annotation, but unlike `#(doc)` it's a **runtime/modeling construct**: it shapes governance, query latency, and correctness, not discoverability. The full reference (syntax, filter types, `required` / `implicit` flags, and when each applies) lives in `malloy-model` § Parameterizable Filters with `#(filter)` alongside the other source-authoring constructs.
81
+ `#(filter)` is deprecated in favour of native Malloy `given:` parameters. Do not add new `#(filter)` annotations; the two exceptions are `required` and `implicit`, which `given:` cannot cover yet. See `skill:malloy-model` § Legacy: Parameterizable Filters.
82
+
83
+ `#(filter)` is also a `#(...)`-shaped annotation, but unlike `#(doc)` it's a **runtime/modeling construct**: it shapes governance, query latency, and correctness, not discoverability. The full reference (syntax, filter types, `required` / `implicit` flags, and when each applies) lives in `malloy-model` § Legacy: Parameterizable Filters alongside the other source-authoring constructs, next to the `given:` guidance that replaces it.
82
84
 
83
85
  One rule worth knowing here: filters live on the source, never on the consumer. Ad-hoc reports and notebooks that import a source inherit its filters automatically; they do not (and cannot) declare new ones.
84
86
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-getting-started
3
3
  description: First steps for using a Malloy Publisher deployment through its MCP tools. Use when connecting to Publisher for the first time, when you do not yet know the available environments, packages, or models, or when a user asks what data they can explore. Covers verifying the server, discovering data with malloy_getContext, and running a first grounded query.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-gotchas-modeling
3
3
  description: Common Malloy modeling mistakes and how to avoid them. Read BEFORE writing source definitions, dimensions, measures, or joins. Covers reserved words, NULL checks, date functions, type casts, field management (extend except/accept/rename vs include public/internal/private), and query-based source gotchas.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-gotchas-queries
3
3
  description: Common Malloy query and view mistakes. Read BEFORE writing views, queries, or notebooks. Covers chart constraints, aggregate filters, joined field aliasing, method syntax, and time truncation vs extraction.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-gotchas-rendering
3
3
  description: Common Malloy renderer annotation mistakes. Read BEFORE adding chart annotations, formatting tags, or building dashboards. Covers tag syntax, scale rules, sparkline setup, and big_value patterns.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-html-data-app-embedding
3
3
  description: Embed an in-package HTML data app into a host page or another application, including auto-sizing and auth. Read when embedding a Publisher page via Publisher.embed.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-html-data-app-runtime
3
3
  description: Write the JavaScript that drives an in-package HTML data app, calling Publisher.query, building queries from filter state, and handling results and errors. Read before writing the page's data code.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-html-data-apps
3
3
  description: Build or modify an in-package HTML data app for a Malloy Publisher package (a public/ directory the package serves). Use when the user wants a hand-authored HTML dashboard or web page backed by a package's Malloy models, with no build step.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-lookml-review
3
3
  description: Analyze LookML files as prior art for Malloy modeling. Used during Step 1 (DISCOVER) when .lkml files are present. Coordinates reference files that extract business logic, relationships, and curation decisions. Works with or without a database connection.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-materialization
3
3
  description: Add and debug Malloy Persistence materializations in a package - persist an expensive source so queries read a pre-built table. Read this whenever the user wants to materialize a source, add a persist annotation, speed up a slow source, or asks why a persist source isn't building.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-materialization-tuning
3
3
  description: Optimize a package's Malloy Persistence materializations for cost and performance using the malloy-pub CLI and the materialization history. Recommend what to persist, what to stop persisting, and how to schedule/scope it. Use when the user asks to make a package cheaper or faster, tune persistence, decide what to materialize, or review persist/schedule choices.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-model
3
3
  description: Build Malloy semantic models with base source and joined source files. Use when creating or modifying .malloy files, user asks to "create a malloy model", "add dimensions", "add measures", "create a source", or any Malloy model authoring task.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -169,9 +169,21 @@ source: customer_health is customers extend {
169
169
  | **Dimensions** | Intrinsic to this table only | Cross-source (require joins) |
170
170
  | **Measures** | Single-table aggregations | Cross-source aggregations |
171
171
  | **Joins** | None (or only lookup joins intrinsic to the source) | Defines relationships between base sources |
172
- | **Views** | None (views belong in analysis) | None |
172
+ | **Views** | None, in schema-first (see below) | None, in schema-first (see below) |
173
173
  | **One per** | Physical table or computed source | Analytical domain |
174
174
 
175
+ **The "no views" rule is schema-first only.** A schema-first model is built before anyone
176
+ has asked a question, so any view in it is a guess. It does **not** apply to the
177
+ analysis-first workflow (`skill:malloy-model-as-you-go`), where every view is a
178
+ question that was asked and verified - there, **saving a view or a dashboard is the right
179
+ call**, in the model file next to the measures it uses. Analysis-first still models
180
+ everything else properly: documented dimensions, measures, and joins.
181
+
182
+ **Two more rules here are schema-first only.** Analysis-first should skip access modifiers
183
+ and curation - there is no discovery surface to curate when every field was paid for by a
184
+ question - and skip one-file-per-table, keeping a single domain file until it genuinely
185
+ gets unwieldy.
186
+
175
187
  ## Key Rules
176
188
 
177
189
  - **Every `dimension:` needs `name is expr`**: a bare `dimension: species` is a parse error. Raw columns are queryable directly in `group_by` / `select`; only declare a `dimension:` to derive or rename a field.
@@ -0,0 +1,306 @@
1
+ ---
2
+ name: malloy-model-as-you-go
3
+ description: After answering a data question, write down what the answer assumed so the next reader can trust the number. A field with a #(doc) in the model when you can edit it, an extend in the notebook when you can only author reports, or a stated assumption plus a Malloy snippet when you can only chat. Use after every answered question that rested on a judgment call, and whenever a question is asked against tables that have no model yet.
4
+ version: 0.1.10
5
+ ---
6
+
7
+ <!--
8
+ Copyright (c) Credible Data Inc.
9
+ SPDX-License-Identifier: MIT
10
+ -->
11
+
12
+ # Model as you go
13
+
14
+ An analysis that lives in a chat transcript is not reproducible. The numbers were right, and
15
+ six weeks later nobody can say what "revenue" excluded, which of four timestamps was the order
16
+ date, or whether the last period was complete. The work is unauditable, so it gets redone.
17
+
18
+ The fix is to write the assumptions down while the query is still in front of you, in the
19
+ most durable place your session can write. Answer the question, codify what the answer
20
+ assumed, answer the next one. After a handful of questions there is a model, or a notebook,
21
+ where every definition exists because a real question needed it, and every judgment call is
22
+ on the record.
23
+
24
+ This skill is the codify step. `skill:malloy-analysis` answers the question; this skill
25
+ decides what to write down afterwards, and where.
26
+
27
+ > **Tool names** are bare here - `get_context`, `execute_query`, `search_database_schema`. The
28
+ > exact prefixed name depends on the host; match against the tools you actually have.
29
+
30
+ ## The loop
31
+
32
+ ```
33
+ QUESTION → ANSWER → CODIFY ⟲
34
+ ↑___________|
35
+ ```
36
+
37
+ **The user gets a real answer on question one.** If three tool calls have gone by without
38
+ producing an insight they can read, you have drifted into modelling for its own sake. Stop and
39
+ answer something.
40
+
41
+ ## 1. Answer the question, with `skill:malloy-analysis`
42
+
43
+ This skill starts when someone asks a data question. **That question is the unit of work, and
44
+ it is theirs.** Do not widen it into a modelling project, and do not swap it for a more
45
+ interesting one you found on the way.
46
+
47
+ **A broad ask is still an ask.** "Analyse the sales data" is a question whose subject is given
48
+ and whose metric is not. Pick the most obvious question about that subject, say in one line
49
+ which one you picked, and answer it. Never open with a row-count table and a menu of
50
+ "analytical domains"; the menu is worth less than the first real answer would have been.
51
+
52
+ Load `skill:malloy-analysis` and follow it: discover the model, construct the query, run it,
53
+ verify it, present it. Two of its rules matter most here:
54
+
55
+ - **Every number you present comes out of a query.** Adding up the rows of a `limit: 15` table
56
+ by hand drops everything below the cut, and nobody can re-run it.
57
+ - **Your first result is a draft.** Work `skill:malloy-analysis-pitfalls` before presenting.
58
+
59
+ ### Name the decisions the answer rests on
60
+
61
+ **This is what makes the analysis auditable, and it is the step most easily skipped.**
62
+
63
+ Almost every query needs at least one judgment call the data cannot settle. Which of four
64
+ timestamps is "the order date". Whether returns count as revenue. Whether to count lines or
65
+ orders. Whether a partial final period belongs in a trend.
66
+
67
+ A judgment call made silently becomes a hidden assumption. It will be wrong for someone
68
+ eventually, and by then nobody remembers it was a choice rather than a fact. So make the call,
69
+ run with it, and **say what you chose and what you rejected**, with the other number attached
70
+ whenever it is cheap to get:
71
+
72
+ > Revenue here excludes cancelled **and** returned orders - that's $8.10M. Counting returns as
73
+ > revenue and netting them separately gives $9.18M. I went with the stricter one; say if your
74
+ > reporting does it the other way.
75
+
76
+ Not every choice rises to this. Raise it when **any** of these holds:
77
+
78
+ | Raise it when | Because |
79
+ |---|---|
80
+ | The alternative changes the number materially | The user would answer differently depending on which they meant |
81
+ | You are about to codify it | It stops being your choice and becomes everyone's definition |
82
+ | A reasonable analyst would pick the other one | It is a convention, not a fact |
83
+
84
+ Otherwise state it in a clause and move on. **Asking about everything is as bad as asking about
85
+ nothing**; it turns the loop into a form and trains the reader to skim past it. And **give the
86
+ number under each option, not an abstract question**: "$8.10M excluding them, $9.18M
87
+ including" lets the user answer in one word.
88
+
89
+ ## 2. When there is no model yet
90
+
91
+ If the tables have no model, define only enough to run the first query. One line is normal:
92
+
93
+ ```malloy
94
+ source: order_items is my_conn.table('ecommerce.order_items')
95
+ ```
96
+
97
+ **Read the columns with `run: source -> { select: * limit: 3 }`.** Prefer it to a schema
98
+ listing: it returns the columns *and* real values, and the values are what catch the surprises
99
+ a column list hides - a date stored as a string, a `total` that excludes tax, a metric column
100
+ that is null on every row.
101
+
102
+ If a schema tool returns something you cannot explain (no columns, or no tables for a filter
103
+ you can see matches), that is a bug in the tool. Report it. Do not write the workaround into
104
+ your model or your notes as though it were a property of the data.
105
+
106
+ No `primary_key:`, no dimensions, no measures, no joins, **not yet**. Those arrive in CODIFY,
107
+ each one paid for by a question that needed it. Adding fields because the table has them is the
108
+ habit this skill exists to break.
109
+
110
+ Add a join only when *this* question cannot be answered without it, then verify its cardinality
111
+ before trusting any aggregate (`group_by: fk, aggregate: n is count(), having: n > 1`).
112
+
113
+ If a model already exists, ground yourself in it with `get_context` and reuse what is there.
114
+
115
+ ## 3. CODIFY: write the answer's assumptions down
116
+
117
+ Run this after *every* answered question, while the query is still in front of you. Skipping it
118
+ is how a session ends with a transcript and nothing else.
119
+
120
+ ### Where it goes depends on what you can write
121
+
122
+ The same assumption lands in a different place depending on the session. Pick the **highest
123
+ rung your tools allow**, and never skip codifying because the top rung is out of reach.
124
+
125
+ | You can | Codify as | Who inherits it |
126
+ |---|---|---|
127
+ | **Edit the model files** (a local package, a draft package, a compile or reload tool) | A `dimension:`, `measure:`, `join_*:`, or `view:` with a `#(doc)`, in the `.malloy` file | Everyone who queries the model. **Confirm binding decisions first** (below) |
128
+ | **Author notebooks or reports, but not the model** (a viewer of a published package) | In the notebook: `source: orders_q is orders extend { measure: ... }` with the `#(doc)` above it, plus a markdown cell stating the assumption. A question worth re-asking becomes a cell | Readers of the report. State the decision in the cell, and say which definitions the model should adopt |
129
+ | **Only answer in chat** (no file or report tools) | The assumption stated in the answer, plus the Malloy snippet a modeler could paste: the `measure:` with its `#(doc)` | Nobody, until someone acts on it. That is why the snippet matters |
130
+
131
+ Tell the rungs apart by the tools you have, not by guessing at the user's role: a file-write or
132
+ compile tool means the top rung; a report or notebook tool without model edits means the middle
133
+ one; neither means the bottom.
134
+
135
+ ### What to codify
136
+
137
+ Look at the query you just ran. For each dimension, measure, join, filter, **and for the shape
138
+ of the query itself**, ask **in this order**:
139
+
140
+ | Codify it when | Example |
141
+ |---|---|
142
+ | **A reader needs it to trust the number** | the cancelled/returned exclusion - without it every revenue figure reads high |
143
+ | **It encodes a business rule** | a regex parsing a messy column, a status mapping, tier cutoffs |
144
+ | **Another question would reuse it** | `revenue is sum(sale_price)` - everything about orders needs it |
145
+ | **It was hard to get right** | a window function, a multi-step derivation, a verified join |
146
+
147
+ The first row is the one that matters. A definition is worth keeping less because it saves
148
+ typing than because it stops the next reader misreading the number.
149
+
150
+ **Codify thin, and leave the ad-hoc behind**: filters tied to one finding, calculations that
151
+ answered exactly one question. A question that needed one measure codifies one measure, not the
152
+ neighbouring columns as well. An empty CODIFY is a fine outcome; say so and move on.
153
+
154
+ **Then say what you did**, in one line, every time:
155
+
156
+ > Codified: `revenue is sum(sale_price)` excluding cancelled and returned, `category` via the
157
+ > products join. Left ad-hoc: the `where: created_at > @2023` - just this question's window.
158
+
159
+ That line shows the model growing and gives the reader a place to object. Never codify
160
+ silently.
161
+
162
+ ### A decision becomes binding the moment it enters the model
163
+
164
+ While a judgment call lives in one ad-hoc query it is yours, and stating it is enough. Once it
165
+ is in the model file it is the definition everyone inherits, and nobody downstream sees the
166
+ reasoning. So on the top rung, **stop, ask, and wait for an answer** before writing it down.
167
+ Reporting it afterwards ("I used the midpoint, say if you'd rather have the floor") is not
168
+ confirming it; by then it is already the default. On the middle rung, confirm when the report
169
+ will be shared; a private notebook is still yours.
170
+
171
+ This is not only about measures. Confirm anything that changes what later questions return:
172
+
173
+ | Confirm before codifying | Because it silently sets |
174
+ |---|---|
175
+ | A source-level `where:` | the scope of *every* later question against that source |
176
+ | A `measure:` definition | the default value of that metric for everyone |
177
+ | A `dimension:` that buckets, maps, or parses | which rows land in which group |
178
+ | A `join_one:`/`join_many:` and its grain | whether aggregates fan out |
179
+ | A saved `view:` | the shape people will re-run and cite |
180
+
181
+ Ask with the numbers attached:
182
+
183
+ > I want to make `net_revenue` exclude cancelled and returned. That makes it the default revenue
184
+ > number for every later question - $8.10M rather than $10.81M gross. Good, or does your
185
+ > reporting treat returns differently?
186
+
187
+ **One confirmation per decision, not per question.** Once the user has settled how revenue
188
+ treats returns, it is settled: reuse it and stop asking. And if the user has told you to stop
189
+ checking in, believe them; state each decision in a clause and keep going.
190
+
191
+ ### Write it down twice
192
+
193
+ **In the model (or the notebook), as a `#(doc)` on the definition.** This is the part that
194
+ survives. Use `#(doc)`, never a `//` comment, for anything a consumer needs: `#(doc)` is
195
+ machine-readable, so it renders in the UI and retrieval tools read it, while a `//` comment
196
+ reaches nobody but whoever opens the file. Keep `//` for maintainer-only notes. Tag formats on
197
+ the definition too: bare `# currency` on the measure, any scale (`# currency=usd0m`) only in
198
+ views.
199
+
200
+ ```malloy
201
+ #(doc) Order line items joined to products. Grain is one row per line, not per order.
202
+ source: order_items is my_conn.table('ecommerce.order_items') extend {
203
+ join_one: products is my_conn.table('ecommerce.products') on product_id = products.id
204
+
205
+ dimension:
206
+ #(doc) Canonical order date. Data runs 2019-01-05 to 2026-03-14, so the final year is PARTIAL - never present it as a full period.
207
+ order_date is created_at::date
208
+
209
+ measure:
210
+ #(doc) Revenue in USD, excluding cancelled and returned orders. Those are 25% of gross, so excluding them is not optional.
211
+ # currency
212
+ net_revenue is sum(sale_price) { where: status != 'Cancelled' and status != 'Returned' }
213
+
214
+ #(doc) Monthly revenue trend. The question asked on 2026-03-02.
215
+ # line_chart
216
+ view: revenue_trend is {
217
+ group_by: order_month is order_date.month
218
+ aggregate:
219
+ # currency=usd0m
220
+ net_revenue
221
+ order_by: order_month
222
+ }
223
+ }
224
+ ```
225
+
226
+ One file per analytical domain, named for it: `order_revenue.malloy`, not `model.malloy`. It
227
+ grows monotonically across the session.
228
+
229
+ **In a notes file, as the reasoning.** Keep an `analysis-notes.md` beside the model (or a
230
+ markdown cell in the notebook) recording each question, what it found, what got codified and
231
+ what was left ad-hoc, and every verification finding. The model carries the *what*; the notes
232
+ carry the *why* and the evidence. It is also what lets you resume after losing context.
233
+
234
+ ### Save the view when a question is worth re-asking
235
+
236
+ A saved `view:` turns "we answered that once" into "re-run it". A trend wanted again next
237
+ month belongs in the file as a `view:` with its chart tag (`skill:malloy-charts`); views wanted
238
+ side by side belong in a notebook (`skill:malloy-notebooks`), or in a dashboard surface if your
239
+ host has one. A genuine one-off does not.
240
+
241
+ > **This departs from `skill:malloy-model` on purpose.** Its "no views in source files" rule
242
+ > assumes a schema-first model, written before anyone asked a question, so its views would be
243
+ > guesses. Here every view is a question that was asked and verified. Two more of its rules do
244
+ > not apply either: skip access modifiers and curation (there is no discovery surface to curate
245
+ > when every field was paid for by a question), and keep one domain file rather than one file
246
+ > per table until it genuinely gets unwieldy. Everything else in `skill:malloy-model` applies:
247
+ > `#(doc)` on every field, verified join cardinality, `nullif` on division, a `given:` for a
248
+ > runtime parameter.
249
+
250
+ ### Then loop
251
+
252
+ Go back to the question, and let what you just found sharpen the next one:
253
+
254
+ > Revenue is concentrated in three categories. Worth asking whether that's new - want the same
255
+ > cut by year?
256
+
257
+ ## When the analysis is going to be shared
258
+
259
+ Docs are not on this list; they happen in CODIFY, as you go. When the user wants to hand it
260
+ over:
261
+
262
+ 1. **Names.** Rename anything whose name only made sense inside one question.
263
+ 2. **Doc pass.** Re-read the `#(doc)` lines for anything that drifted as the model grew. Check
264
+ grain, units, and null handling are each stated somewhere.
265
+ 3. **Re-verify.** Re-run the session's key queries against the finished sources. If a number
266
+ moved, something was codified wrong. This is the check that proves reproducibility; do not
267
+ skip it.
268
+ 4. **Structure**, only if one file has genuinely become unwieldy: base sources per table plus a
269
+ joined source per domain, per `skill:malloy-model`. Being shared is not itself a reason.
270
+
271
+ ## When to do something else
272
+
273
+ | Situation | Go to |
274
+ |---|---|
275
+ | Porting prior art (LookML, dbt, a metrics doc): the definitions exist and are agreed, the job is translation | `skill:malloy-lookml-review`, then `skill:malloy-model` |
276
+ | The user names the sources they want built outright, before any question | `skill:malloy-model` |
277
+ | A model already exists, the question rests on no judgment call, and nothing is worth keeping | `skill:malloy-analysis` alone |
278
+ | Open-ended exploration with no intent to keep anything | `skill:malloy-analyze` |
279
+
280
+ ## Anti-patterns
281
+
282
+ ```
283
+ WRONG Turn "what's our default rate?" into a modelling project
284
+ RIGHT Answer it, then codify what the answer assumed
285
+
286
+ WRONG Propose every dimension and measure for each table in scope
287
+ RIGHT Codify the two fields this question actually needed
288
+
289
+ WRONG Quietly pick one of four timestamps as "the order date"
290
+ RIGHT "Using created_at; shipped_at would drop 38% as nulls. OK?"
291
+
292
+ WRONG Skip codifying because you cannot edit the model
293
+ RIGHT Put the extend and its #(doc) in the notebook, or hand over the snippet
294
+
295
+ WRONG Codify the source-level where:, then mention it in the write-up
296
+ RIGHT Stop and confirm it; it scopes every question anyone asks later
297
+
298
+ WRONG The assumption lives in the chat transcript, or in a // comment
299
+ RIGHT #(doc) on the definition for what a consumer needs, plus a line in the notes
300
+
301
+ WRONG Leave the answered question as a transcript table and move on
302
+ RIGHT Save it as a view; a question worth answering is usually worth re-asking
303
+
304
+ WRONG Curate access modifiers and split one file per table to "do it properly"
305
+ RIGHT Skip both; they solve a schema-first problem this model does not have
306
+ ```
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-modeling
3
3
  description: Build semantic models with Malloy for the Malloy Publisher. Read this skill whenever the user asks about modeling data or specifically mentions Malloy.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -96,9 +96,8 @@ Publishing is out of scope for open-source v1. Self-hosters move a finished mode
96
96
 
97
97
  **Two paths to a model: both produce the same fully documented result:**
98
98
  - **Schema-first:** "Model my data" → 8-step workflow above using the relevant skills
99
- - **Analysis-first:** "Explore this data" → `skill:malloy-analyze` → formalize via `skill:malloy-model` (`reference/analysis-to-model.md`)
100
-
101
- After analysis completes, **always recommend formalizing into a model.**
99
+ - **Analysis-first:** a data question arrives before any model exists → `skill:malloy-model-as-you-go`. It answers the question with `skill:malloy-analysis`, then codifies what the answer assumed into the model, one question at a time, confirming binding decisions first. The model exists by the end; there is no separate formalize step.
100
+ - **Open-ended exploration** with no intent to keep anything: `skill:malloy-analyze`. If it turns into something worth keeping, formalize via `skill:malloy-model` (`reference/analysis-to-model.md`).
102
101
 
103
102
  ## Agent Behavior
104
103
 
@@ -120,9 +119,9 @@ After analysis completes, **always recommend formalizing into a model.**
120
119
  | "Model from LookML" | 8-step with prior art via `skill:malloy-lookml-review` |
121
120
  | "Explore this data", "what's interesting?", "show me the top X" | `skill:malloy-analyze` (EDA) |
122
121
  | "Build a dashboard", "create views" on existing model | `skill:malloy-analyze` (views), plus `skill:malloy-charts` or `skill:malloy-notebooks` as needed |
123
- | "Build a model but not sure what metrics" | `skill:malloy-analyze` first, then formalize via `skill:malloy-model` |
122
+ | "Build a model but not sure what metrics" | `skill:malloy-model-as-you-go`: answer their first real question, codify what it assumed, repeat |
124
123
 
125
- **If the user's first message is a data question** (not "build me a model"), route to `skill:malloy-analyze`. After analysis completes, **always recommend formalizing via the analysis-to-model workflow** (`skill:malloy-model` → `reference/analysis-to-model.md`).
124
+ **If the user's first message is a data question** (not "build me a model"), route to `skill:malloy-model-as-you-go`. It answers with `skill:malloy-analysis` and grows the model from what each answer assumed, so there is nothing to formalize afterwards.
126
125
 
127
126
  ## Additional Support Skills
128
127
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-notebook-chat
3
3
  description: Steps to follow when the chat is bound to a notebook or saved report. The notebook's cells are the agent's primary context, answer from it, run its queries, and only reach for get_context when the user asks about something outside it.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-notebooks
3
3
  description: Create Malloy notebooks (.malloynb) for interactive dashboards and data stories. Use when user asks to "create a notebook", "make a dashboard notebook", "write a malloynb", "data story", or needs to build reports/visualizations.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-patterns
3
3
  description: Index of Malloy documentation topics. Use to discover what's available in search_malloy_docs. Covers language reference (sources, queries, views, fields, aggregates, joins, filters, expressions, functions), common patterns (YoY, cohorts, percent of total), rendering, and experimental features.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-phrase-detection
3
3
  description: How to construct search targets for the get_context tool. Covers target-type classification and non-obvious decomposition patterns. Read the tool description for field definitions and the end-to-end workflow.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-publish
3
3
  description: Package Malloy models for serving by Malloy Publisher. Use when user asks to "publish", "package", "deploy", or wants to share models with others.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-queries
3
3
  description: Malloy query patterns, syntax rules, and chart annotation reference. Consult before writing or debugging any query. Covers dates, aggregates vs dimensions, join paths, filters, string matching, and common error patterns.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-review
3
3
  description: Malloy semantic-model code review. Invoke when the user asks to review, audit, or critique a `.malloy` file, a folder of Malloy models, or a GitHub PR that touches Malloy. Enforces project modeling standards and emits a navigable review file.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: malloy-scope
3
3
  description: Present discovery findings and propose an analytical scope before modeling. Use after inspecting a package's model and data, to classify tables and recommend an analytical focus the user can pick from.
4
- version: 0.1.9
4
+ version: 0.1.10
5
5
  ---
6
6
  <!--
7
7
  Copyright (c) Credible Data Inc.