@malloy-publisher/skills 0.1.0

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +68 -0
  3. package/dist/index.d.ts +22 -0
  4. package/dist/index.js +47 -0
  5. package/package.json +54 -0
  6. package/skills/malloy/SKILL.md +45 -0
  7. package/skills/malloy-analysis/SKILL.md +76 -0
  8. package/skills/malloy-analysis-pitfalls/SKILL.md +68 -0
  9. package/skills/malloy-analysis-report/SKILL.md +125 -0
  10. package/skills/malloy-analyze/SKILL.md +262 -0
  11. package/skills/malloy-charts/SKILL.md +417 -0
  12. package/skills/malloy-debug/SKILL.md +121 -0
  13. package/skills/malloy-define/SKILL.md +194 -0
  14. package/skills/malloy-discover/SKILL.md +182 -0
  15. package/skills/malloy-document/SKILL.md +165 -0
  16. package/skills/malloy-getting-started/SKILL.md +57 -0
  17. package/skills/malloy-gotchas-modeling/SKILL.md +277 -0
  18. package/skills/malloy-gotchas-queries/SKILL.md +132 -0
  19. package/skills/malloy-gotchas-rendering/SKILL.md +124 -0
  20. package/skills/malloy-html-data-app-embedding/SKILL.md +39 -0
  21. package/skills/malloy-html-data-app-runtime/SKILL.md +158 -0
  22. package/skills/malloy-html-data-apps/SKILL.md +165 -0
  23. package/skills/malloy-html-data-apps/reference/lazy-load.md +84 -0
  24. package/skills/malloy-html-data-apps/reference/verification-harness.md +121 -0
  25. package/skills/malloy-lookml-review/SKILL.md +79 -0
  26. package/skills/malloy-lookml-review/reference/_concepts.md +42 -0
  27. package/skills/malloy-lookml-review/reference/build-derived-tables.md +110 -0
  28. package/skills/malloy-lookml-review/reference/build-unnest.md +47 -0
  29. package/skills/malloy-lookml-review/reference/curate-visibility.md +66 -0
  30. package/skills/malloy-lookml-review/reference/discover.md +102 -0
  31. package/skills/malloy-lookml-review/reference/document.md +28 -0
  32. package/skills/malloy-lookml-review/reference/propose-fields.md +75 -0
  33. package/skills/malloy-lookml-review/reference/review-coverage.md +102 -0
  34. package/skills/malloy-materialization-tuning/SKILL.md +103 -0
  35. package/skills/malloy-model/SKILL.md +320 -0
  36. package/skills/malloy-model/reference/access-modifiers.md +21 -0
  37. package/skills/malloy-model/reference/analysis-to-model.md +82 -0
  38. package/skills/malloy-model/reference/bridge-tables.md +51 -0
  39. package/skills/malloy-model/reference/normalized-schemas.md +66 -0
  40. package/skills/malloy-model/reference/query-sources.md +57 -0
  41. package/skills/malloy-modeling/SKILL.md +174 -0
  42. package/skills/malloy-notebook-chat/SKILL.md +19 -0
  43. package/skills/malloy-notebooks/SKILL.md +225 -0
  44. package/skills/malloy-patterns/SKILL.md +117 -0
  45. package/skills/malloy-phrase-detection/SKILL.md +75 -0
  46. package/skills/malloy-publish/SKILL.md +111 -0
  47. package/skills/malloy-queries/SKILL.md +270 -0
  48. package/skills/malloy-review/SKILL.md +160 -0
  49. package/skills/malloy-review/reference/output-template.md +164 -0
  50. package/skills/malloy-review/reference/rubric-correctness.md +161 -0
  51. package/skills/malloy-review/reference/rubric-documentation.md +69 -0
  52. package/skills/malloy-review/reference/rubric-governance.md +37 -0
  53. package/skills/malloy-review/reference/rubric-queries.md +78 -0
  54. package/skills/malloy-review/reference/rubric-rendering.md +127 -0
  55. package/skills/malloy-review/reference/rubric-structure.md +68 -0
  56. package/skills/malloy-review/reference/rubric-style.md +59 -0
  57. package/skills/malloy-review/reference/scope-resolution.md +100 -0
  58. package/skills/malloy-review/reference/severity-taxonomy.md +139 -0
  59. package/skills/malloy-scope/SKILL.md +100 -0
@@ -0,0 +1,42 @@
1
+ # LookML → Malloy Concept Mapping
2
+
3
+ Reference table for translating LookML constructs to Malloy. Referenced by multiple reference files.
4
+
5
+ | LookML | Malloy | Notes |
6
+ |--------|--------|-------|
7
+ | `view:` | `source:` (base source file) | One source per physical table |
8
+ | `explore:` | `source:` (source file with joins) | One source per analytical domain |
9
+ | `dimension:` | `dimension:` | Direct mapping |
10
+ | `dimension_group: { type: time }` | `.month`, `.year`, `::date` (native) | Malloy handles time natively; no explicit timeframe list needed |
11
+ | `dimension: { type: yesno }` | `dimension: x is condition` | Boolean expression |
12
+ | `measure: { type: count }` | `count()` | Always distinct in Malloy |
13
+ | `measure: { type: count_distinct }` | `count(field)` | Direct mapping |
14
+ | `measure: { type: sum }` | `sum(field)` | Direct mapping |
15
+ | `measure: { type: average }` | `avg(field)` | Direct mapping |
16
+ | `measure: { type: number }` | Derived measure expression | Usually a ratio; use `nullif()` for division |
17
+ | `measure: { filters: [...] }` | `measure { where: condition }` | Filtered aggregate |
18
+ | `primary_key: yes` | `primary_key: field_name` | Direct mapping |
19
+ | `hidden: yes` | `# hidden` tag (cosmetic) | Classify reason first; see `curate-visibility.md` |
20
+ | `fields` exclusion (explore/join) | `internal:` (with access modifiers) | Structurally excluded; `internal:` candidate |
21
+ | `required_access_grants` | `private:` (with access modifiers) | Security-restricted; `private:` candidate |
22
+ | `description:` | `#(doc)` tag | Direct mapping |
23
+ | `label:` (simple rename) | `internal:` old + `dimension: new_name is old_name` | Never use `rename:` |
24
+ | `label:` (complex) | `# label="Display Name"` | When name differs from identifier |
25
+ | `sql_table_name:` | `conn.table('schema.table')` | Use the connection name from the model definition if available |
26
+ | `join: { relationship: many_to_one }` | `join_one:` | Direct mapping |
27
+ | `join: { relationship: one_to_one }` | `join_one:` | Direct mapping |
28
+ | `join: { relationship: one_to_many }` | `join_many:` | Direct mapping |
29
+ | `join: { relationship: many_to_many }` | `join_cross:` | Direct mapping |
30
+ | `sql_on: ${a.field} = ${b.field}` | `on a_field = b.b_field` | Translate `${}` references |
31
+ | `CASE WHEN ... END` (in SQL) | `pick ... when ... else` | Direct syntax translation |
32
+ | `COALESCE(a, b)` | `a ?? b` | Direct mapping |
33
+ | `IFNULL(a, b)` | `a ?? b` | Direct mapping |
34
+ | `${TABLE}.field` | `field` (direct column reference) | Malloy references columns directly |
35
+ | `${view_name.field}` | `view_name.field` (join path) | In join conditions and cross-source refs |
36
+ | `+view:` (refinement) | User decides: consolidate or `extend` | Malloy `extend` serves the same purpose |
37
+ | `derived_table: { sql: ... }` (perf-only) | Use base table directly | PDT optimization is Looker-specific |
38
+ | `derived_table: { sql: ... }` (transformation) | Flag for user | Recommend base table + dims or upstream dbt |
39
+ | `derived_table: { explore_source: ... }` (NDT) | `from(source -> { group_by:, aggregate: }) extend { }` | Computed source pattern |
40
+ | `value_format: "$#,##0.00"` | `# currency` | Map to Malloy render tags |
41
+ | `value_format: "0.00%"` | `# percent` | Map to Malloy render tags |
42
+ | `value_format_name: decimal_2` | `# number="0.00"` | Map to Malloy render tags |
@@ -0,0 +1,110 @@
1
+ # LookML Derived Table Conversion (Step 5)
2
+
3
+ > Classify LookML derived tables and convert them to Malloy patterns. Reference `_concepts.md` for syntax translation. This runs during Step 5 (BUILD) when the prior-art notes flag derived tables.
4
+
5
+ ## Decision Tree
6
+
7
+ ```
8
+ derived_table:
9
+ ├── explore_source: → NDT path
10
+ │ ├── Simple aggregation → Malloy from() extend {}
11
+ │ ├── With derived_column: (window functions) → Malloy window function patterns
12
+ │ ├── Chained NDTs → dependency ordering, multi-stage computed source
13
+ │ └── With bind_filters → flag, no direct Malloy equivalent
14
+ └── sql: → PDT path
15
+ ├── Performance-only → strip to base table
16
+ └── Transformation → recommend base table + Malloy dims or upstream dbt
17
+ ```
18
+
19
+ ## NDT Path (`explore_source:`)
20
+
21
+ ### Simple Aggregation NDT
22
+
23
+ Express the aggregation as a Malloy query, then build a source from it:
24
+
25
+ ```malloy
26
+ source: source_name is from(
27
+ base_source -> {
28
+ group_by: group_field
29
+ aggregate:
30
+ metric_one is count()
31
+ metric_two is sum(amount_field)
32
+ }
33
+ ) extend {
34
+ primary_key: group_field
35
+ dimension: derived_dim is metric_one > 1
36
+ }
37
+ ```
38
+
39
+ ### NDT with `derived_column:` (Window Functions)
40
+
41
+ LookML `derived_column:` adds window functions on top of the NDT result. Map to Malloy window function patterns. Call `search_malloy_docs("window functions")` for current syntax.
42
+
43
+ ### Chained NDTs
44
+
45
+ When one NDT references another's explore_source, determine the dependency order. Build the upstream computed source first, then reference it in the downstream one. Each becomes its own `.malloy` file.
46
+
47
+ ### NDTs with `bind_filters`
48
+
49
+ No direct Malloy equivalent. Flag for user and propose alternatives:
50
+ - Parameterized source (if Malloy supports it; check `search_malloy_docs`)
51
+ - Pre-filtered views
52
+ - Document the intent and let the user decide
53
+
54
+ ## PDT Path (`sql:`)
55
+
56
+ ### Performance-Only PDT
57
+
58
+ **Signal:** SQL is essentially `SELECT *, generate_uuid() as pk FROM table WHERE increment_condition` with partitioning/clustering/incremental keys.
59
+
60
+ **Action:** Use the base table directly. The PDT optimization is Looker-specific. Strip to the actual `FROM` table, resolve any manifest constants (`@{TABLE_NAME}`), and use `conn.table('resolved_table')`.
61
+
62
+ **Synthetic PK investigation:** If the PDT generates a synthetic PK (`generate_uuid()`, `concat(field_a, field_b)`), the actual grain is unknown. Run queries to determine it:
63
+
64
+ ```malloy
65
+ // Check if candidate columns form a unique grain
66
+ run: source -> {
67
+ group_by: candidate_pk_field
68
+ aggregate: row_count is count()
69
+ having: row_count > 1
70
+ order_by: row_count desc
71
+ limit: 10
72
+ }
73
+ ```
74
+
75
+ ### Transformation PDT
76
+
77
+ **Signal:** SQL has JOINs, CTEs, complex WHERE clauses, or computed columns.
78
+
79
+ **Action:** Flag for user. Recommend:
80
+ 1. Use the base table + Malloy dimensions for the computed columns
81
+ 2. Push the transformation to upstream dbt/SQL
82
+ 3. Only carry forward verbatim if user insists (last resort)
83
+
84
+ **CRITICAL: Never use `conn.sql()` when Malloy has a native pattern.** For aggregation, window functions (`calculate`), and filtering, use Malloy query-based sources (`table -> { group_by, aggregate } extend {}`). `conn.sql()` is a last resort for patterns with NO Malloy equivalent (UNNEST, PIVOT, dialect-specific functions). Call `search_malloy_docs` before writing any SQL block.
85
+
86
+ ## Long→wide entity-values pivot (custom fields)
87
+
88
+ A common LookML shape: an entity-attribute-value (EAV) table (custom fields / properties) that LookML widened by joining the same table **N times, once per attribute**. Don't port those N joins. Pivot with **filtered aggregates** in a single query-based source — one aggregate per attribute value, no repeated joins:
89
+
90
+ ```malloy
91
+ // EAV table: (entity_id, field_name, field_value) — one row per attribute.
92
+ // Wide result: one row per entity, one column per attribute of interest.
93
+ source: entity_custom_fields is conn.table('custom_field_values') -> {
94
+ group_by: entity_id
95
+ aggregate:
96
+ industry is field_value.max() { where: field_name = 'industry' }
97
+ tier is field_value.max() { where: field_name = 'tier' }
98
+ account_owner is field_value.max() { where: field_name = 'account_owner' }
99
+ } extend {
100
+ primary_key: entity_id
101
+ }
102
+ ```
103
+
104
+ `field_value.max()` (the `expr.aggregate() { where: … }` filtered-aggregate form — same shape as `x.sum() { where: … }` in `skill:malloy-gotchas-modeling`) collapses the one matching row per attribute to a scalar; `max` is a native Malloy aggregate that works on strings. Do **not** reach for `any_value`/`ANY_VALUE` — that's a warehouse SQL function, not a Malloy aggregate, and would force a raw-SQL escape that (per the median gotcha in `skill:malloy-gotchas-modeling`) doesn't compile in aggregate position anyway.
105
+
106
+ Then `join_one` this once onto the entity source. This replaces LookML's N self-joins with one grouped scan — fewer joins, one pass, and new attributes are one more `aggregate:` line. (If an attribute can legitimately repeat per entity, that's not a wide column — model it as a nested/joined detail instead.)
107
+
108
+ ## Examples
109
+
110
+ Grounded in real patterns from `lookml/block-google-cloud-billing/` and `lookml/bq_thelook/`.
@@ -0,0 +1,47 @@
1
+ # LookML UNNEST & Struct Conversion (Step 5)
2
+
3
+ > Convert LookML UNNEST joins and struct field access patterns to Malloy. This runs during Step 5 (BUILD) when the prior-art notes flag UNNEST joins or struct access.
4
+
5
+ ## UNNEST Join Patterns
6
+
7
+ LookML joins containing `UNNEST()` in the `sql:` parameter are unnesting nested/repeated fields (common in BigQuery). Example from `gcp_billing_export.explore.lkml`:
8
+
9
+ ```
10
+ # LookML: unnests a repeated field
11
+ join: gcp_billing_export__labels {
12
+ sql: LEFT JOIN UNNEST(${gcp_billing_export.labels}) as gcp_billing_export__labels ;;
13
+ relationship: one_to_many
14
+ }
15
+ ```
16
+
17
+ Malloy handles nested structures natively. The conversion depends on whether you want to:
18
+ 1. **Keep nested**: access nested fields directly via the parent source
19
+ 2. **Flatten**: create a separate source from the unnested data
20
+
21
+ Call `search_malloy_docs("nested repeated")` for current Malloy syntax for nested/repeated field handling.
22
+
23
+ ## Struct Field Access
24
+
25
+ LookML accesses struct fields via `${TABLE}.struct.field` syntax:
26
+
27
+ ```
28
+ # LookML
29
+ dimension: project_id { sql: ${TABLE}.project.id ;; }
30
+ dimension: adjustment_description { sql: ${TABLE}.adjustment_info.description ;; }
31
+ ```
32
+
33
+ In Malloy, struct field access uses dot notation directly. Check `search_malloy_docs("struct")` for the current syntax.
34
+
35
+ ## Multiple Views from One Table
36
+
37
+ LookML often creates separate views for each unnested array (e.g., `gcp_billing_export__labels`, `gcp_billing_export__credits`, `gcp_billing_export__system_labels`). These all derive from the same base table.
38
+
39
+ **Options:**
40
+ 1. **Consolidate**: model as nested sources within the parent source
41
+ 2. **Keep separate**: create individual sources if they have distinct analytical value
42
+
43
+ Present options to the user with the tradeoffs.
44
+
45
+ ## Flattened Naming Conventions
46
+
47
+ LookML flattens nested field names with double underscores: `project__id`, `labels__key`, `labels__value`. In Malloy, use cleaner names that reflect the actual structure. Propose renames during Step 4 field proposals.
@@ -0,0 +1,66 @@
1
+ # LookML Visibility → Malloy Access Modifiers (Step 8)
2
+
3
+ > Classify LookML visibility mechanisms and map them to Malloy access modifiers. This runs during Step 8 (CURATE) when the prior-art notes have a Visibility Seeds section.
4
+
5
+ ## LookML Visibility Mechanisms
6
+
7
+ | LookML Mechanism | Semantics | Strength |
8
+ |---|---|---|
9
+ | `hidden: yes` | Hidden from Explore field picker, but still queryable via URL/API | Cosmetic: UI decluttering |
10
+ | `fields` (explore-level) | Excluded from an explore's available field pool. Not queryable. Can use `ALL_FIELDS*` with `-field` exclusions. | Structural: field genuinely inaccessible |
11
+ | `fields` (join-level) | Restricts which fields from a joined view enter the explore. Include-list only. | Structural: field never enters the pool |
12
+ | `fields_hidden_by_default: yes` | All view fields hidden unless individually opted in with `hidden: no` | Cosmetic: bulk hide |
13
+ | `required_access_grants` | User-attribute-based restriction. True security mechanism. | Security: access controlled |
14
+
15
+ ## Classification → Malloy Treatment
16
+
17
+ For each hidden/excluded field, determine the **reason** and map to the correct Malloy treatment:
18
+
19
+ | Classification | LookML Signal | Malloy Treatment |
20
+ |---|---|---|
21
+ | **Intermediate calculation** | `hidden: yes` + referenced by other fields via `${field}` | `# hidden` tag: keep accessible, hide from display |
22
+ | **Join key / FK** | `hidden: yes` + used in `sql_on:` conditions | Keep public, no `#(doc)`: needed for joins |
23
+ | **UI clutter** | `hidden: yes` + not referenced elsewhere | Assess: if truly unused, `internal:` candidate; if just verbose, `# hidden` |
24
+ | **Explore-scoped exclusion** | Excluded via `fields` parameter | `internal:` candidate: field was structurally inaccessible |
25
+ | **Bulk hidden** | `fields_hidden_by_default: yes` on view | Treat each field individually based on its role |
26
+ | **Access-restricted** | `required_access_grants` | `private:` candidate: flag for user decision |
27
+ | **Not hidden** | No visibility restriction | Public + `#(doc)` candidate |
28
+
29
+ ## Key Distinction
30
+
31
+ LookML `hidden: yes` ≈ Malloy `# hidden` (cosmetic). LookML `fields` exclusion ≈ Malloy `internal:` (structural).
32
+
33
+ **Do NOT map `hidden: yes` directly to `internal:`.** That over-restricts fields that may be needed as intermediate calculations or join keys.
34
+
35
+ ## Pattern: Restricted-field masking (`include { private: } + pick`)
36
+
37
+ When a sensitive field should be *present in a redacted/bucketed form* rather than dropped, don't reach for an access modifier alone: modifiers hide or expose, they don't transform. Keep the raw column **`private:`** and derive a masked dimension from it in the source's immediate `extend {}`: a `private:` field is referenceable from the immediate extension, so the raw value stays out of the queryable surface while the mask is public. The mask must key on **data the model can see**, a row-level column or a source `parameter`, not on the viewer:
38
+
39
+ ```malloy
40
+ ##! experimental.access_modifiers
41
+ source: revenue is conn.table('…') include {
42
+ private: raw_amount // raw stays out of the queryable surface…
43
+ } extend {
44
+ dimension: amount is // …but is referenceable here, in the immediate extension
45
+ pick null when is_confidential // is_confidential is a ROW column, not the viewer
46
+ else raw_amount
47
+ // or bucket instead of null-out:
48
+ // pick 'under 50k' when raw_amount < 50000
49
+ // pick '50k–100k' when raw_amount < 100000
50
+ // else '100k+'
51
+ }
52
+ ```
53
+
54
+ Give the mask a **different name** from the private raw column (`amount` vs `raw_amount`), reusing the name is a redefinition. If the mask must carry the raw column's *exact* original name, that forces a `rename:`, which pushes you onto the `extend { rename }` path that does **not** compose with `include {}` (see `skill:malloy-gotchas-modeling`); there the raw column can only be `# hidden` (dropped from display but still queryable), so prefer a distinct mask name and keep the raw `private:`.
55
+
56
+ **This is NOT per-viewer access control.** LookML `required_access_grants` gates on the *viewing user's* attributes; Malloy models have no per-viewer context, so a model-layer `pick` can only redact based on row data or a parameter. To gate a field by *caller identity/role* (the real `required_access_grants` equivalent), use `#(authorize)` on the source, driven by trusted attributes in Malloy Publisher (see `skill:malloy-model` § Access Control). Masking and `#(authorize)` are independent layers; use the mask for "everyone sees a coarsened value," `#(authorize)` for "only some callers see the field at all."
57
+
58
+ ## Pattern: Long→wide custom-field pivot
59
+
60
+ See `build-derived-tables.md` → "Long→wide entity-values pivot" for turning an entity-attribute-value (EAV) custom-field table into wide columns with **filtered aggregates**, the right move when LookML modeled N per-attribute joins.
61
+
62
+ ## Process
63
+
64
+ 1. Read the Visibility Seeds notes captured during discovery
65
+ 2. For each field, apply the classification rules above
66
+ 3. Present the classified list to the user for confirmation before applying access modifiers
@@ -0,0 +1,102 @@
1
+ # LookML Discovery (Step 1)
2
+
3
+ > Inventory a LookML project, classify its contents, extract architecture-level candidates, and capture prior-art notes in the conversation. Does NOT extract individual field definitions; that's deferred to `propose-fields.md`.
4
+
5
+ ## 1. Locate `.lkml` Files
6
+
7
+ Scan the project directory and immediate subdirectories (especially `lookml/`, `lkml/`, `looker/`, or any directory containing `.lkml` files). Ask the user to confirm: "I found LookML files. Use as prior art?"
8
+
9
+ ## 2. Categorize by File Type
10
+
11
+ | File Pattern | Type | Priority |
12
+ |-------------|------|----------|
13
+ | `manifest.lkml` | Manifest | Read FIRST: contains constants |
14
+ | `*.model.lkml` | Model | Connection name, includes, datagroups |
15
+ | `*.view.lkml` | View | Source-level knowledge (dimensions, measures) |
16
+ | `*.explore.lkml` | Explore | Relationship-level knowledge (joins) |
17
+ | `*.dashboard.lookml` | Dashboard | **SKIP entirely**: analysis is a separate workflow |
18
+
19
+ ## 3. Resolve Manifest Constants
20
+
21
+ Read `manifest.lkml` and build a lookup table. LookML references like `@{BILLING_TABLE}` must be resolved to actual table names before analysis.
22
+
23
+ ```
24
+ constant: BILLING_TABLE {
25
+ value: "instance.billing.gcp_billing_export_public"
26
+ }
27
+ # → @{BILLING_TABLE} resolves to "instance.billing.gcp_billing_export_public"
28
+ ```
29
+
30
+ ## 4. Extract Connection Info
31
+
32
+ From model files, note the `connection:` value. This is the **LookML connection name**; it may differ from the connection name used in the Malloy model. In LookML-only mode, use as fallback and flag as unverified.
33
+
34
+ ## 5. Extract Source Candidates from Views
35
+
36
+ For each `.view.lkml` file, extract architecture-level info only:
37
+
38
+ - **Table reference:** `sql_table_name:` → direct table, `derived_table:` → classify (see `build-derived-tables.md`)
39
+ - **Primary key:** field with `primary_key: yes`
40
+ - **Grain:** inferred from PK and table name
41
+ - **Role:** Fact (has measures, transactional) or Dimension (lookup, one row per base source)
42
+
43
+ ### Identify Refinements
44
+
45
+ - **Base view:** `view: view_name { ... }`
46
+ - **Refinement:** `view: +view_name { ... }`: extends an existing view
47
+
48
+ Present the refinement structure to the user for a consolidate/extend decision.
49
+
50
+ ## 6. Extract Source Candidates from Explores
51
+
52
+ For each explore, extract:
53
+
54
+ - **Base view** and **joined views** with cardinality (`relationship:` → `join_one:` / `join_many:` / `join_cross:`)
55
+ - **Join conditions** (`sql_on:`): translate `${view.field}` references
56
+ - **UNNEST joins**: flag any `sql:` containing `UNNEST()` (BigQuery nested fields)
57
+ - **`sql_always_where:`**: document as context, do NOT bake into Malloy
58
+ - **`hidden: yes`** on explores: flag for scope decisions
59
+
60
+ ## 7. Quality Evaluation: Flag Situations
61
+
62
+ Classify patterns into KEEP, SKIP, and FLAG categories:
63
+
64
+ **KEEP:** dimension/measure names, aggregation formulas, PKs, join relationships, simple SQL refs, CASE/WHEN logic, filtered aggregates, value_format patterns, yesno dimensions.
65
+
66
+ **SKIP:** `link:`, `drill_fields:`, `html:`, `action:`, `parameter:` (note intent), Liquid templates (strip, note intent), `datagroup_trigger:`, PDT optimization keys, `convert_tz:`, dashboards, `view_label:`, `group_item_label:`.
67
+
68
+ **FLAG for user decision:**
69
+
70
+ | Pattern | Default Recommendation |
71
+ |---------|----------------------|
72
+ | Complex SQL dimensions (50+ lines) | Simplify or push upstream |
73
+ | `derived_table { sql: ... }` with complex SQL | Classify as performance-only vs transformation |
74
+ | `sql_always_where:` constraints | Document as context, don't bake in |
75
+ | Synthetic primary keys (`generate_uuid()`, `concat()`) | Ask about actual grain |
76
+ | Refinement structure (`+view` across files) | Consolidate or preserve layering? |
77
+
78
+ ## 8. Extract Visibility Seeds
79
+
80
+ Scan for fields with non-default visibility. Capture a compact summary (not full field extraction):
81
+
82
+ - `hidden: yes` fields: note whether they're join keys, intermediate calcs, or UI clutter
83
+ - `fields` parameter exclusions (explore/join level)
84
+ - `required_access_grants`: flag for user decision
85
+
86
+ ## 9. Extract Documentation Seeds
87
+
88
+ Scan for fields with high-quality `description:` values worth preserving as `#(doc)` tags. Capture source, field name, and the description text.
89
+
90
+ ## 10. Capture Prior-Art Notes
91
+
92
+ Hold a lean routing summary in the conversation with these sections:
93
+
94
+ - **Source**: Type, Location, Mode, Confidence
95
+ - **Source Candidates**: table of sources with table, grain, PK, role
96
+ - **Source Candidates**: table of sources with base source, joins, notes
97
+ - **Flags**: numbered list of situations requiring attention
98
+ - **Visibility Seeds**: compact table of non-default visibility fields
99
+ - **Documentation Seeds**: compact table of fields with good descriptions
100
+ - **Decisions Made During Discovery**: refinement choice, connection name resolution, etc.
101
+
102
+ No field-level detail beyond seeds. Architecture + flags + decisions only.
@@ -0,0 +1,28 @@
1
+ # LookML Documentation Seeds (Step 9)
2
+
3
+ > Extract LookML descriptions and formatting hints as starting material for Malloy `#(doc)` tags. This runs during Step 9 (DOCUMENT) when the prior-art notes have a Documentation Seeds section.
4
+
5
+ ## Process
6
+
7
+ 1. **Start from seeds**: read the Documentation Seeds captured during discovery for fields with high-quality LookML `description:` values.
8
+
9
+ 2. **Read `.lkml` view files** for the full `description:` text on dimensions and measures not captured in seeds.
10
+
11
+ 3. **Rewrite for retrieval**: LookML descriptions are written for Looker's field picker. Malloy `#(doc)` strings power AI search. Rewrite to match how analysts search:
12
+ - Use business meaning, not Looker jargon
13
+ - Include units (USD, count, percentage) and valid values for categorical fields
14
+ - Avoid "filterable", "groupable", "dimension", "measure"
15
+
16
+ 4. **Map formatting to render tags:**
17
+
18
+ | LookML | Malloy |
19
+ |--------|--------|
20
+ | `value_format: "$#,##0.00"` | `# currency` |
21
+ | `value_format: "0.00%"` | `# percent` |
22
+ | `value_format_name: decimal_2` | `# number="0.00"` |
23
+ | `value_format_name: usd` | `# currency` |
24
+ | `value_format_name: percent_2` | `# percent` |
25
+
26
+ 5. **Map labels:**
27
+ - `label:` that simply renames → `internal:` + `dimension:` candidate
28
+ - `label:` that differs from a clean snake_case name → `# label="Display Name"` candidate
@@ -0,0 +1,75 @@
1
+ # LookML Field Extraction (Step 4)
2
+
3
+ > Read `.lkml` view files and extract field proposals for the Malloy model. Reference `_concepts.md` for type mapping. This runs during Step 4 (PROPOSE DEFINITIONS) to provide LookML-sourced field proposals alongside schema-derived proposals.
4
+
5
+ ## Prerequisites
6
+
7
+ - Prior-art notes exist with Type: lookml and a Location path
8
+ - Read `reference/_concepts.md` for the LookML → Malloy type mapping table
9
+
10
+ ## Extract Fields from Views
11
+
12
+ Read every `.view.lkml` file at the location from the prior-art notes. For each view in scope (including refinements merged onto their base view):
13
+
14
+ ### For Each Field, Capture:
15
+
16
+ | Attribute | What to Capture | Maps to |
17
+ |-----------|----------------|---------|
18
+ | Field name | The LookML field name | Dimension/measure name candidate |
19
+ | `type:` | Data type (string, number, date, yesno, etc.) | Malloy type hint |
20
+ | `sql:` | The SQL expression | Dimension/measure logic |
21
+ | `description:` | Business description | `#(doc)` tag content |
22
+ | `label:` | Display name (if different from field name) | `internal:` + `dimension:` or `# label=` candidate |
23
+ | `hidden: yes` | Hidden from Explore picker | Classify per `curate-visibility.md` |
24
+ | `group_label:` | Logical grouping | Source organization hint |
25
+ | `primary_key: yes` | Primary key | `primary_key:` |
26
+ | `value_format:` / `value_format_name:` | Display formatting | `# currency`, `# percent`, `# number` |
27
+ | `filters:` (on measures) | Filtered aggregate | `{ where: ... }` syntax |
28
+
29
+ ### Handle `dimension_group` (Time Dimensions)
30
+
31
+ LookML `dimension_group` with `type: time` generates multiple dimensions from one timestamp column. Malloy handles time natively. Extract:
32
+ - The **base column** from `sql:` (e.g., `${TABLE}.created_at`)
33
+ - Note declared timeframes (signals which granularities were used)
34
+ - No explicit dimension needed for each timeframe in Malloy
35
+
36
+ ### Handle `type: yesno`
37
+
38
+ Boolean flags derived from a SQL condition:
39
+ ```
40
+ # LookML
41
+ dimension: is_complete { type: yesno sql: ${status} = 'complete' ;; }
42
+
43
+ # Malloy
44
+ dimension: is_complete is status = 'complete'
45
+ ```
46
+
47
+ ### Handle Measure Types
48
+
49
+ | LookML Measure Type | Malloy Equivalent | Notes |
50
+ |---------------------|-------------------|-------|
51
+ | `type: count` | `count()` | Malloy `count()` is always distinct |
52
+ | `type: count_distinct` | `count(field)` | Direct mapping |
53
+ | `type: sum` | `sum(field)` | Direct mapping |
54
+ | `type: average` | `avg(field)` | Direct mapping |
55
+ | `type: min` / `max` | `min(field)` / `max(field)` | Direct mapping |
56
+ | `type: number` | Derived measure expression | Usually a ratio; use `nullif()` for division |
57
+ | `type: list` | No direct equivalent | Flag for alternative approach |
58
+ | `type: percentile` | `field.percentile(n)` | Direct mapping |
59
+
60
+ **Filtered measures:** `filters:` → Malloy `{ where: }` syntax:
61
+ ```
62
+ # LookML
63
+ measure: completed_revenue { type: sum sql: ${total} ;; filters: [status: "completed"] }
64
+
65
+ # Malloy
66
+ measure: completed_revenue is sum(total) { where: status = 'completed' }
67
+ ```
68
+
69
+ ## Field-Level Visibility Classification
70
+
71
+ For each field with visibility restrictions, classify per `curate-visibility.md` rules and include the classification in the proposal's `Malloy Treatment` column.
72
+
73
+ ## Present Proposals
74
+
75
+ Present field proposals with a `Provenance: lookml` column alongside schema-derived proposals. For fields flagged with Liquid/parameter usage in the prior-art notes, note the business intent without trying to convert the mechanism.
@@ -0,0 +1,102 @@
1
+ # LookML Coverage Review (Step 7)
2
+
3
+ > Compare the built Malloy model against the original LookML project. Show the user what was modeled, what was skipped, and why. This runs during Step 7 (REVIEW) when prior-art notes exist.
4
+
5
+ ## Data Sources
6
+
7
+ Read these before building the coverage report:
8
+
9
+ | Source | What it tells you |
10
+ |--------|------------------|
11
+ | Original `.lkml` files (location from the prior-art notes) | Full inventory of views, explores, dimensions, measures |
12
+ | Prior-art notes | Source candidates, flags, visibility seeds |
13
+ | Definition notes | What was proposed, confirmed, and deferred |
14
+ | Built `.malloy` files in package root | What actually shipped |
15
+
16
+ ## 1. Source Coverage
17
+
18
+ Compare LookML views against Malloy base source files. Every LookML view should appear in this table.
19
+
20
+ | LookML View | Malloy Source | Status | Rationale |
21
+ |-------------|--------------|--------|-----------|
22
+ | orders | orders.malloy | modeled | (none) |
23
+ | users | customers.malloy | modeled (renamed) | Renamed to match business terminology |
24
+ | order_items | (none) | deferred | Bridge table: defer until line-item analysis needed |
25
+ | admin_audit | (none) | skipped:not-analytical | Operational/ETL table |
26
+ | order_facts_pdt | user_order_facts.malloy | modeled (rearchitected) | Performance-only PDT stripped; rebuilt as computed source |
27
+
28
+ **Status values:**
29
+ - `modeled`: directly represented in a Malloy source
30
+ - `modeled (renamed)`: represented under a different name
31
+ - `modeled (rearchitected)`: LookML pattern was restructured (e.g., PDT → computed source)
32
+ - `deferred`: valid source, postponed to a later iteration
33
+ - `skipped:not-analytical`: operational, staging, or ETL table
34
+ - `skipped:pre-aggregated`: snapshot/summary table; compute fresh in Malloy
35
+ - `skipped:looker-specific`: view exists only for Looker UI purposes
36
+
37
+ ## 2. Field Coverage (Per Modeled Source)
38
+
39
+ For each modeled source, walk the original LookML view's dimensions and measures. Show what mapped and what didn't.
40
+
41
+ | LookML Field | Type | Malloy Field | Status |
42
+ |-------------|------|-------------|--------|
43
+ | order_id | dimension (PK) | order_id | modeled |
44
+ | total_price | measure (sum) | revenue | modeled (renamed) |
45
+ | status | dimension | order_status | modeled (renamed) |
46
+ | created_month | dimension_group | order_month (.month) | modeled (native) |
47
+ | status_link | dimension | (none) | skipped:looker-ui |
48
+ | period_comparison | measure | (none) | skipped:liquid |
49
+ | _pk | dimension | (none) | skipped:synthetic-key |
50
+
51
+ **Status values:**
52
+ - `modeled`: direct mapping
53
+ - `modeled (renamed)`: mapped under a clearer name
54
+ - `modeled (native)`: LookML pattern replaced by native Malloy feature (e.g., dimension_group → `.month`)
55
+ - `deferred`: valid field, not included in this iteration
56
+ - `skipped:looker-ui`: `link:`, `drill_fields:`, `html:`, `action:` patterns
57
+ - `skipped:liquid`: Liquid templating with no direct equivalent (intent documented)
58
+ - `skipped:synthetic-key`: generated PK, not meaningful as a dimension
59
+ - `skipped:duplicate`: redundant field, another field covers the same data
60
+ - `skipped:upstream`: complex SQL that belongs in dbt/ETL, not the semantic layer
61
+
62
+ **Show field counts per base source** to give a quick coverage ratio:
63
+
64
+ > **orders:** 18 of 24 LookML fields modeled (75%). 6 skipped: 3 Looker UI, 2 Liquid, 1 synthetic key.
65
+
66
+ ## 3. Source/Explore Coverage
67
+
68
+ Compare LookML explores against Malloy source files.
69
+
70
+ | LookML Explore | Malloy Source | Status | Rationale |
71
+ |----------------|--------------|--------|-----------|
72
+ | order_analysis | order_analysis.malloy | modeled | (none) |
73
+ | customer_health | customer_health.malloy | modeled | (none) |
74
+ | admin_overview | (none) | skipped:not-analytical | Operational dashboard explore |
75
+
76
+ Include join coverage within each modeled source: did all the LookML joins carry over?
77
+
78
+ ## 4. Skipped Patterns Summary
79
+
80
+ Group all skipped items by reason with counts. This gives the user a quick sense of what categories of things were left out and whether any warrant reconsideration.
81
+
82
+ | Category | Count | Examples |
83
+ |----------|-------|---------|
84
+ | Looker UI patterns (link, drill, html, action) | 12 | link on order_id, drill on customer_name |
85
+ | Liquid templating | 3 | period_over_period parameter, dynamic_timeframe |
86
+ | Filter-only fields | 2 | status_filter, date_filter |
87
+ | Synthetic keys | 1 | _pk (generate_uuid) |
88
+ | Dashboard files | 1 | overview.dashboard.lookml |
89
+ | Upstream SQL | 2 | complex_attribution, geo_enrichment |
90
+
91
+ ## 5. Presentation
92
+
93
+ Present coverage in this order:
94
+
95
+ 1. **Overall summary**: "Modeled X of Y sources, covering Z% of LookML fields. N items deferred, M skipped."
96
+ 2. **Source coverage table** (Section 1)
97
+ 3. **Per-source field counts** with ratios (Section 2 summaries)
98
+ 4. **Skipped patterns summary** (Section 4)
99
+ 5. **Deferred items**: list everything marked `deferred` across sources and fields, with rationale, so the user knows what's available for future iterations
100
+ 6. **Full field coverage tables** (Section 2 detail): present per base source if the user wants to drill in
101
+
102
+ **Ask the user:** "Does this coverage look right? Anything deferred that you'd like to add now, or anything modeled that should be removed?"