@metabase/cli 0.1.15 → 0.1.17
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.
- package/README.md +69 -32
- package/dist/{add-collection-CXhrMjXU.mjs → add-collection-C-t9SQBk.mjs} +5 -5
- package/dist/add-collection-Dek6kiRI.mjs +11 -0
- package/dist/{archive-BUwiCFnd.mjs → archive-C7dnyzVY.mjs} +9 -7
- package/dist/{archive-Blc1dz8Z.mjs → archive-CaoUUTIb.mjs} +8 -7
- package/dist/{archive-D-Ovk0-E.mjs → archive-D1FX-sbU.mjs} +7 -6
- package/dist/{archive-CQiaCBSj.mjs → archive-DS-KEB4a.mjs} +7 -6
- package/dist/{archive-yTvDRQUZ.mjs → archive-LG8u7ec5.mjs} +7 -6
- package/dist/{archive-BxGSR7El.mjs → archive-qSOcACQo.mjs} +8 -6
- package/dist/{archive-Drt-rWvU.mjs → archive-wNXwIiF0.mjs} +8 -7
- package/dist/auth-Kv2MRkRk.mjs +22 -0
- package/dist/{body-B9XTTDEu.mjs → body-IcJ5kFtk.mjs} +3 -3
- package/dist/{branches-Ct3PK5U3.mjs → branches-BlNCTmFB.mjs} +7 -6
- package/dist/{cancel-tST8Bh0w.mjs → cancel-BnveTPNw.mjs} +6 -5
- package/dist/{cancel-task-BHnzRQKL.mjs → cancel-task-CSVI8Zgl.mjs} +7 -6
- package/dist/{capabilities-BX1rnVuH.mjs → capabilities-N0jo5U7S.mjs} +1 -1
- package/dist/card-Bp58WEUF.mjs +26 -0
- package/dist/{card-wCPcuKSi.mjs → card-DEmcRlNO.mjs} +6 -5
- package/dist/{cards-BySIFsLG.mjs → cards-CMgGA8OZ.mjs} +8 -6
- package/dist/cli.mjs +54 -26
- package/dist/collection-D8TI20Ir.mjs +23 -0
- package/dist/{collection-namespace-D8ohM5gD.mjs → collection-namespace-CsaxEqOb.mjs} +2 -2
- package/dist/command-augment-DdZIfx1V.mjs +11 -0
- package/dist/{create-HZ-9GMLx.mjs → create-AeWNv0v-.mjs} +9 -8
- package/dist/create-B1xfaZJB.mjs +35 -0
- package/dist/{create-DxDD6n1d.mjs → create-B6LQutd0.mjs} +17 -12
- package/dist/{create-C7FPeJqF.mjs → create-B79M8YpX.mjs} +10 -9
- package/dist/{create-DGxZmbxh.mjs → create-Bg_uMu0p.mjs} +9 -8
- package/dist/{create-C43RQYgj.mjs → create-BpkWlgoR.mjs} +18 -12
- package/dist/{create-DnT1Sayk.mjs → create-C7u3umLj.mjs} +9 -8
- package/dist/{create-4mAGsBd0.mjs → create-FZOrCw5k.mjs} +21 -12
- package/dist/{create-fIDFkdox.mjs → create-I5Cv-MBa.mjs} +16 -11
- package/dist/{create-D3cVDtxM.mjs → create-LHqwcVbS.mjs} +16 -11
- package/dist/{create-BDWthCYq.mjs → create-SHKlj0z3.mjs} +9 -8
- package/dist/{create-branch-jPE7nW-E.mjs → create-branch-CGyT99Ny.mjs} +7 -6
- package/dist/{current-task-rW54KJax.mjs → current-task-C-0hw2Ae.mjs} +7 -6
- package/dist/dashboard-C9pQCnX6.mjs +28 -0
- package/dist/{dashboard-B4bn3z6t.mjs → dashboard-DOplbKyQ.mjs} +7 -5
- package/dist/{database-BmsxuRmO.mjs → database-D9fftP-i.mjs} +1 -1
- package/dist/db-DWykmBOh.mjs +28 -0
- package/dist/{delete-BWO6uB4P.mjs → delete--NYYN6wv.mjs} +8 -7
- package/dist/{delete-CeigtfRI.mjs → delete-Do3nn9sl.mjs} +8 -7
- package/dist/{delete-runtime-PzLFavb0.mjs → delete-runtime-B0ha5QR4.mjs} +3 -3
- package/dist/{delete-CBwztF6P.mjs → delete-tCVrYjKD.mjs} +8 -7
- package/dist/{delete-table-BXzvDEPG.mjs → delete-table-CWSGPw0i.mjs} +8 -7
- package/dist/{dependencies-D2N9eqm1.mjs → dependencies-CyFocD8I.mjs} +7 -6
- package/dist/{dirty-DoEZRSGB.mjs → dirty-BXM0YJ_a.mjs} +7 -6
- package/dist/document-DNDm8_Py.mjs +22 -0
- package/dist/{eid-1mROiwJr.mjs → eid-p-zn_1RJ.mjs} +13 -7
- package/dist/{error-D5bZ5BUX.mjs → error-5H_tcfL8.mjs} +2 -2
- package/dist/{export-Bgv5B4_F.mjs → export-B_8wghyo.mjs} +9 -8
- package/dist/field-CFt1-KDR.mjs +21 -0
- package/dist/{fields-VA7tXHJz.mjs → fields-BsufL9kv.mjs} +8 -7
- package/dist/{get-DAsaor0n.mjs → get-4lufgahL.mjs} +7 -6
- package/dist/{get-B8etVxWD.mjs → get-68P-L4ja.mjs} +7 -6
- package/dist/{get-qnGGGRTa.mjs → get-B45uYMAs.mjs} +8 -6
- package/dist/get-B67pe00Z.mjs +36 -0
- package/dist/{get-DvQoDU2m.mjs → get-BJoZkATD.mjs} +7 -6
- package/dist/{get-BgIE_YEg.mjs → get-BTKeO4vE.mjs} +7 -6
- package/dist/{get-RQFSmnpY.mjs → get-C7mq17-2.mjs} +7 -6
- package/dist/{get-CGkhSY5L.mjs → get-CDLa6LiC.mjs} +9 -8
- package/dist/{get-D9o38nK5.mjs → get-CPw0ROO4.mjs} +7 -6
- package/dist/{get-Wj88mnhn.mjs → get-Cl5Ak73C.mjs} +9 -7
- package/dist/{get-CqnCaosn.mjs → get-D3k2JUsa.mjs} +7 -6
- package/dist/{get-C-UeGtgW.mjs → get-DTqBuIf3.mjs} +6 -5
- package/dist/{get-eUYDNALt.mjs → get-DeLnNVQE.mjs} +8 -7
- package/dist/{get-DLRBq8kR.mjs → get-Df_3LCVC.mjs} +7 -6
- package/dist/{get-BtAD26gc.mjs → get-DurjkUKJ.mjs} +7 -6
- package/dist/{get-run-CATz-XMS.mjs → get-run-Dq4qfGfD.mjs} +7 -6
- package/dist/git-sync-ByvjqSiy.mjs +31 -0
- package/dist/group-BNE_RiH5.mjs +28 -0
- package/dist/{has-remote-changes-B6YEMC90.mjs → has-remote-changes-E-4N_O_t.mjs} +7 -6
- package/dist/{import-Bl2Wj9Xf.mjs → import-Dg0j3HCP.mjs} +9 -8
- package/dist/{input-BXWgdKiS.mjs → input-7Sj85_K7.mjs} +1 -1
- package/dist/is-dirty-D7UMN0mt.mjs +10 -0
- package/dist/{is-dirty-D9MoFj0T.mjs → is-dirty-DWw1yBeR.mjs} +4 -4
- package/dist/{items-LjtICjvX.mjs → items-CQtt9X4M.mjs} +9 -8
- package/dist/{key-C8q5oIr5.mjs → key-bltP32Pm.mjs} +1 -1
- package/dist/library-B5AvpACG.mjs +24 -0
- package/dist/{list-ChK06rJh.mjs → list-B1uVWy6A.mjs} +8 -6
- package/dist/{list-DaJk60dd.mjs → list-BAOTQHst.mjs} +6 -5
- package/dist/{list-C410Ptw_.mjs → list-BF-W4jOZ.mjs} +9 -7
- package/dist/{list-DMik04Dh.mjs → list-BFVuPodI.mjs} +6 -5
- package/dist/{list-DfoNv0gb.mjs → list-BHAWYZ1z.mjs} +6 -5
- package/dist/{list-DcjenlTM.mjs → list-BKUvhAs4.mjs} +6 -5
- package/dist/{list-Dq86BAMG.mjs → list-BgUWqZa2.mjs} +8 -7
- package/dist/{list-CfWblVHJ.mjs → list-C4bALfCs.mjs} +7 -6
- package/dist/{list-CoRX8Ruc.mjs → list-C9O2RY5u.mjs} +6 -5
- package/dist/{list-cnqPlJgC.mjs → list-CfqpDTna.mjs} +6 -5
- package/dist/{list-C6U3Ftg8.mjs → list-CvhVYOVl.mjs} +7 -6
- package/dist/{list-B5Q7E2Km.mjs → list-D3TSAqwl.mjs} +6 -5
- package/dist/list-D5Gdz8hi.mjs +100 -0
- package/dist/{list-Bjp9nd0o.mjs → list-DG0FIhTK.mjs} +6 -5
- package/dist/{list-DtrfE0sF.mjs → list-KxQNqp4T.mjs} +8 -7
- package/dist/{login-Cn2UtFUt.mjs → login-Bt6j6yBM.mjs} +10 -9
- package/dist/{logout-_HXgEmqA.mjs → logout-ANkL02p0.mjs} +6 -5
- package/dist/{manifest-DYeLri80.mjs → manifest-BVf8P4bl.mjs} +9 -2
- package/dist/measure-CgFwtL1r.mjs +25 -0
- package/dist/{metadata-8yqT8OZI.mjs → metadata-ClehtgZj.mjs} +9 -8
- package/dist/{metadata-DHODjg1d.mjs → metadata-DplshwI3.mjs} +8 -7
- package/dist/{command-augment-CAur0XOQ.mjs → notice-DyVl5aYB.mjs} +1 -11
- package/dist/parameter-CiJ4CwWE.mjs +118 -0
- package/dist/parameter-values-D4J8Ctu0.mjs +56 -0
- package/dist/{parse-enum-BatHQ-Gs.mjs → parse-enum-BL9i_brN.mjs} +1 -1
- package/dist/{parse-id-DNPeV0Iu.mjs → parse-id-D4LeTUsP.mjs} +1 -1
- package/dist/{parse-ref-CZr1bYIl.mjs → parse-ref-CB_KvF9h.mjs} +1 -1
- package/dist/{path-BflajM08.mjs → path-5nQgdvrs.mjs} +6 -5
- package/dist/{poll-CYkX02Bm.mjs → poll-BpAJpvb-.mjs} +2 -2
- package/dist/{poll-task-oXpl3wzp.mjs → poll-task-TilgciQn.mjs} +2 -2
- package/dist/{preflight-BdJr2amA.mjs → preflight-OfHU3Toi.mjs} +4 -4
- package/dist/{process-DsGf7Mg5.mjs → process-j8UHMHc2.mjs} +1 -1
- package/dist/{prompt-Bc_bHSD0.mjs → prompt-C85xd9HR.mjs} +1 -1
- package/dist/publish-DKiqyDwo.mjs +67 -0
- package/dist/{query-CxTjguwp.mjs → query-BWJ5h1g3.mjs} +19 -13
- package/dist/{query-DWQaN7hO.mjs → query-CQ3xXa9P.mjs} +10 -8
- package/dist/{query-result-L5_NrwQR.mjs → query-result-D6mfoVfQ.mjs} +1 -1
- package/dist/{remove-collection-BwcFi8Fs.mjs → remove-collection-CO-aMzTO.mjs} +9 -8
- package/dist/{rescan-values-B9Qtsd32.mjs → rescan-values-DpL6LGAK.mjs} +9 -8
- package/dist/resolve-Dj2MTBkn.mjs +84 -0
- package/dist/{run-C-vftXCu.mjs → run-DUcdaZs3.mjs} +6 -5
- package/dist/{run-3eFtdI2Z.mjs → run-QYJ-mAaG.mjs} +9 -8
- package/dist/{runs-CpRTkn1W.mjs → runs-DgasTnGd.mjs} +8 -7
- package/dist/{runtime-C1QgWHM7.mjs → runtime-BJtuxWM8.mjs} +5 -3
- package/dist/{schema-tables-rdeupgsu.mjs → schema-tables-ooYjimrV.mjs} +8 -7
- package/dist/{schemas-Cr7RjYqV.mjs → schemas-HjFPzsd-.mjs} +6 -5
- package/dist/{search-D7Yq2MDu.mjs → search-vaT5EwhG.mjs} +11 -5
- package/dist/segment-BPC725mo.mjs +25 -0
- package/dist/{selectors-DPc2ZDdd.mjs → selectors-DlmZpo2L.mjs} +4 -4
- package/dist/{set-X2N6x7pe.mjs → set-CLHJauzG.mjs} +9 -8
- package/dist/{set-active-BoBa3pOw.mjs → set-active-oZOUe9V7.mjs} +6 -5
- package/dist/setting-DHYO-W5g.mjs +20 -0
- package/dist/{setup-C47WUieB.mjs → setup-D1de5Sbc.mjs} +8 -7
- package/dist/{skills-CJ8g8fhQ.mjs → skills-B6gfH0iR.mjs} +1 -1
- package/dist/{skills-Po0YPVU5.mjs → skills-NhgVypJ7.mjs} +3 -3
- package/dist/snippet-DtLmHr2J.mjs +22 -0
- package/dist/{stash-BV4sEp73.mjs → stash-DoRwelwY.mjs} +9 -8
- package/dist/{status-BKF9rRnk.mjs → status-BpMOlfsA.mjs} +8 -7
- package/dist/{status-Cf8c0NON.mjs → status-CmRgHTrV.mjs} +6 -5
- package/dist/{summary-RgLcBcEh.mjs → summary-BEu7pmpq.mjs} +7 -6
- package/dist/{sync-schema-DKEel2Sv.mjs → sync-schema-DTUXubMg.mjs} +11 -10
- package/dist/table-B1iBqmNu.mjs +22 -0
- package/dist/{table-qDD2kApF.mjs → table-DE3i82T_.mjs} +9 -2
- package/dist/transform-BuIooRQh.mjs +31 -0
- package/dist/transform-job-C3yz0krA.mjs +25 -0
- package/dist/transform-tag-CJD6mJUe.mjs +21 -0
- package/dist/{transforms-BOQ9CG0l.mjs → transforms-CWvMpLc9.mjs} +7 -6
- package/dist/{tree-CF8mscd9.mjs → tree-D18vYSe4.mjs} +6 -5
- package/dist/{unpublish-CmaRNjK3.mjs → unpublish-CJbL4ZsC.mjs} +20 -18
- package/dist/{update-Kz4kyqY_.mjs → update-7DacwIvi.mjs} +10 -9
- package/dist/{update-CgQOSuZt.mjs → update-BjjZIe2W.mjs} +10 -9
- package/dist/{update-nwXgVNQa.mjs → update-Bos8nnv0.mjs} +18 -13
- package/dist/{update-BXZBEeUp.mjs → update-BvsvyBw9.mjs} +17 -12
- package/dist/{update-DQPg0_-d.mjs → update-CSxwZ2us.mjs} +11 -10
- package/dist/{update-BuypOaXq.mjs → update-Ck0Kxv8u.mjs} +10 -9
- package/dist/{update-B59h7KmS.mjs → update-DV7IY7IQ.mjs} +10 -9
- package/dist/{update-DERsb0eO.mjs → update-DhOfrW1j.mjs} +19 -13
- package/dist/{update-DR4WkAET.mjs → update-Doia9MP_.mjs} +17 -12
- package/dist/{update-rhfPHALN.mjs → update-Dxa_6H2A.mjs} +22 -13
- package/dist/{update-dashcard-BJVeSGvO.mjs → update-dashcard-CZUWZhal.mjs} +11 -9
- package/dist/{update-DoQpy9SA.mjs → update-mr9todHq.mjs} +10 -9
- package/dist/{upgrade-CNrkJF6s.mjs → upgrade-CBW8V9qZ.mjs} +7 -6
- package/dist/{uuid-Dpl3ALKq.mjs → uuid-D6JVJ-R1.mjs} +10 -5
- package/dist/{validate-VawhJ5Sc.mjs → validate-BqNW4Sk1.mjs} +2 -2
- package/dist/{validate-query-CSV-TTnd.mjs → validate-query-CcZVKYPV.mjs} +3 -3
- package/dist/{values-DMYpkoEH.mjs → values-DBvuGnnA.mjs} +7 -6
- package/dist/{verify-By9ZYzx4.mjs → verify-LIShMNZ2.mjs} +2 -2
- package/dist/{wait-BzIAj8lJ.mjs → wait-DUHze3_B.mjs} +8 -7
- package/dist/{wait-flags-DQrxnlwv.mjs → wait-flags-Ybpt98PK.mjs} +2 -2
- package/package.json +1 -1
- package/skill-data/core/SKILL.md +45 -57
- package/skill-data/data-workflow/SKILL.md +116 -0
- package/skill-data/{data-analysis/SKILL.md → data-workflow/references/answering-questions.md} +7 -13
- package/skill-data/{data-transformation/SKILL.md → data-workflow/references/building-clean-tables.md} +46 -48
- package/skill-data/{semantic-layer/SKILL.md → data-workflow/references/reusable-definitions.md} +29 -54
- package/skill-data/document/SKILL.md +10 -20
- package/skill-data/git-sync/SKILL.md +36 -36
- package/skill-data/mbql/SKILL.md +3 -15
- package/skill-data/mbql/references/operators.md +9 -0
- package/skill-data/transform/SKILL.md +26 -42
- package/skill-data/visualization/SKILL.md +6 -6
- package/skill-data/visualization/references/settings.md +12 -0
- package/skills/metabase-cli/SKILL.md +2 -2
- package/dist/add-collection-Bnuh0d4j.mjs +0 -10
- package/dist/auth-Bkue4Psy.mjs +0 -19
- package/dist/card-faWo9ZWU.mjs +0 -20
- package/dist/collection-DcRz7fmf.mjs +0 -20
- package/dist/dashboard-pXAxJTTx.mjs +0 -21
- package/dist/db-CmHHj5eI.mjs +0 -22
- package/dist/document-DQIhL75C.mjs +0 -19
- package/dist/field-B-UcqFzm.mjs +0 -18
- package/dist/git-sync-CwOvcUI2.mjs +0 -28
- package/dist/is-dirty-MT0BT4CS.mjs +0 -9
- package/dist/list-Cx8JxS2C.mjs +0 -42
- package/dist/measure-BIAoqgcS.mjs +0 -19
- package/dist/publish-CGUkIqSm.mjs +0 -70
- package/dist/segment-BN8sFunv.mjs +0 -19
- package/dist/setting-zZjcOPyE.mjs +0 -17
- package/dist/snippet-DK0ZA8m5.mjs +0 -19
- package/dist/table-COAKXNuj.mjs +0 -21
- package/dist/transform-Bto4U82j.mjs +0 -25
- package/dist/transform-job-_fW1y2tg.mjs +0 -22
- package/dist/transform-tag-C8n1oTr1.mjs +0 -18
- package/skill-data/robot-data-engineer/SKILL.md +0 -142
- /package/dist/{body-flags-D7q87Btw.mjs → body-flags-DWTTxJpP.mjs} +0 -0
- /package/dist/{collection-Deiziuu2.mjs → collection-DrLpA1SO.mjs} +0 -0
- /package/dist/{document-qfwR0r63.mjs → document-1W7NRaO_.mjs} +0 -0
- /package/dist/{field-E0IBy4Uw.mjs → field-CMY_LWUe.mjs} +0 -0
- /package/dist/{measure-JJAdFoqK.mjs → measure-DoJvtCaA.mjs} +0 -0
- /package/dist/{paginate-BexjkjbY.mjs → paginate-FVZUxL4J.mjs} +0 -0
- /package/dist/{render-CkuFkWlQ.mjs → render-BTKnWL0d.mjs} +0 -0
- /package/dist/{revision-message-flag-C7zBeWEt.mjs → revision-message-flag-CHrJgFFx.mjs} +0 -0
- /package/dist/{segment-B9SEv9V_.mjs → segment-TXktTCfU.mjs} +0 -0
- /package/dist/{setting-i0pugNHl.mjs → setting-m46MUtW5.mjs} +0 -0
- /package/dist/{snippet-NNzqlksR.mjs → snippet-CtA2Pkoa.mjs} +0 -0
- /package/dist/{transform-n376akp8.mjs → transform-DEF38FWe.mjs} +0 -0
- /package/dist/{transform-job-C8lIS-7Q.mjs → transform-job-CtixL4An.mjs} +0 -0
- /package/dist/{transform-tag-DB46AhAj.mjs → transform-tag-rsIrckCM.mjs} +0 -0
|
@@ -1,38 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion, EnterPlanMode, ExitPlanMode
|
|
5
|
-
---
|
|
1
|
+
# Build clean tables
|
|
2
|
+
|
|
3
|
+
> Part of the **`data-workflow`** skill — the "build clean tables" stage. It assumes that skill's **Shared Contract** (how to communicate, PII, autonomy, permission-denied) and final-recap rule. CLI mechanics: `core` (auth, `field`/`table` verbs, library publish), `mbql` (transform query bodies), `transform` (creating/running transforms).
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
**Contents**
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
- [Two kinds of decisions](#two-kinds-of-decisions)
|
|
8
|
+
- [The process](#the-process) — [Phase 0 — Get Oriented](#phase-0--get-oriented), [Phase 1 — Investigate](#phase-1--investigate-in-plan-mode-if-they-choose), [Phase 2 — Present what you found](#phase-2--present-what-you-found-plain-language), [Phase 3 — Iterate](#phase-3--iterate), [Phase 4 — Build, check, hand back](#phase-4--build-check-hand-back)
|
|
9
|
+
- [A worked decode example](#a-worked-decode-example-for-your-reference-not-the-users)
|
|
10
|
+
- [Cleaning checklist](#cleaning-checklist-for-your-reference-not-the-users)
|
|
10
11
|
|
|
11
|
-
Your job: take a raw source database — usually normalized, often synced from
|
|
12
|
+
Your job: take a raw source database — usually normalized, often synced from a SaaS tool by a connector like Fivetran or Airbyte — and produce a **small set of wide, clean, analysis-ready tables**, one per real-world _thing_ the data is about, built as Metabase **transforms** the user can inspect.
|
|
12
13
|
|
|
13
14
|
Drive everything through the `mb` CLI. Load the skills you'll need:
|
|
14
15
|
|
|
15
16
|
```bash
|
|
16
|
-
mb skills get core # auth, profiles, db/table/field inspection, query
|
|
17
|
+
mb skills get core # auth, profiles, db/table/field inspection, query, library publish
|
|
17
18
|
mb skills get mbql # if you build transform queries in MBQL
|
|
18
19
|
mb skills get transform # creating/running transforms, run inspection
|
|
19
20
|
```
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
If you are making transforms, use the transform skill.
|
|
24
|
-
|
|
25
|
-
---
|
|
26
|
-
|
|
27
|
-
## Who you're talking to
|
|
28
|
-
|
|
29
|
-
A **non-technical user who knows their domain well** — they understand the business (events, customers, invoices, etc.) but not databases.
|
|
22
|
+
Pick the profile per `core`'s **Auth & profiles** and pass `--profile <name>` to every command. That profile's `url` is the instance's base URL; the browser links below are built from it.
|
|
30
23
|
|
|
31
|
-
|
|
32
|
-
- **Don't lean on raw SQL to communicate.** They may follow a simple `SELECT`, but don't explain work via SQL or ask them to read/write it.
|
|
33
|
-
- Group what you show by **the question a column answers**, never by which source table it came from.
|
|
34
|
-
- Be a **helpful assistant, not an engineer reporting status.** Elide machinery; ask sharp questions that matter.
|
|
35
|
-
- Your user may say "go" and come back later. **If you ever ask the user a question, wait for their answer.**
|
|
24
|
+
Two communication habits specific to this work, on top of the Shared Contract: **don't communicate through SQL** — they may follow a simple `SELECT`, but never explain your work via SQL or ask them to read or write it; and **group what you show by the question a column answers**, never by which source table it came from. Be a helpful assistant, not an engineer reporting status — elide the machinery, ask the sharp questions that matter.
|
|
36
25
|
|
|
37
26
|
---
|
|
38
27
|
|
|
@@ -44,11 +33,11 @@ Sort every choice into one of these.
|
|
|
44
33
|
|
|
45
34
|
1. Never flatten multi-valued fields into opaque blobs (e.g. three options squished: `"email | phone | text"`). It destroys filterability (the whole point).
|
|
46
35
|
2. Never use jargon with the user. Explain by domain and telos.
|
|
47
|
-
3. Always surface **real data you're about to leave out** proactively, ranked by how much is extant.
|
|
36
|
+
3. Always surface **real data you're about to leave out** proactively, ranked by how much is extant. (Phase 2(c) is where you present it.)
|
|
48
37
|
4. Never guess what schema mean from their name alone. Confirm against actual values, interpret them in context: the table the field belongs to and the relevant domain (e.g., a status on orders ≠ status on subscriptions).
|
|
49
38
|
5. Never silently drop a whole _thing_. Dropping a column is routine; dropping a whole kind-of-thing (e.g. "suppliers") must be surfaced and confirmed.
|
|
50
|
-
6. Never drop columns that link things together. Every table keeps its own id **and** the ids tying it to other tables — alongside the readable labels you copy in, not instead of
|
|
51
|
-
7. Never bake a non-obvious business rule into a table without confirming it in plain terms. When a transform encodes a judgment the user would have an opinion on — how money nets, which row is the "current" one, what "active" means — say it back in one plain sentence and get a yes/no first. You know only the columns; they know the business
|
|
39
|
+
6. Never drop columns that link things together. Every table keeps its own id **and** the ids tying it to other tables — alongside the readable labels you copy in, not instead of (label for reading, id for joining). You're building tables about _related_ things, so they **will** be combined ("sales per region", "messages per customer") — dropped ids make that quietly impossible. Keep the ids.
|
|
40
|
+
7. Never bake a non-obvious business rule into a table without confirming it in plain terms. When a transform encodes a judgment the user would have an opinion on — how money nets, which row is the "current" one, what "active" means — say it back in one plain sentence and get a yes/no first. You know only the columns; they know the business, and wrong rules hide in clean-looking tables. ("I'm treating each person's most recent sign-up as their current one — right?")
|
|
52
41
|
8. Never sneak sensitive personal data through. Flag it on sight — addresses, phone numbers, emails, IPs, financial, etc. — and ask the user how to handle it (the prudential call below). Always surface, never silently expose it in a table others will browse.
|
|
53
42
|
9. Never overwrite existing tables or other transforms' outputs. Before building, check the target name is unused (`mb transform list`, `mb table list`); if it's in use, stop and surface it — building over it silently destroys their data. Reuse names only for updating _your own_ transform (`transform update`), never for clobbering another.
|
|
54
43
|
|
|
@@ -57,7 +46,7 @@ Sort every choice into one of these.
|
|
|
57
46
|
- **Multi-valued attribute** (one response → many options; one order → many line items): keep it filterable! Structured columns for predefined lists, or simple join tables, never opaque text. Structure is the user's call. Lean: easiest filtering, probably flat.
|
|
58
47
|
- **Layering**: default **flat** — one self-contained table per thing, no hidden intermediate tables. Suggest a shared cleaned-up base table only for DRY, avoiding copying complex logic across many transforms. Even then, ask.
|
|
59
48
|
- **Out-of-scope things**: surface every domain-model you find and ask in/out, rather than inferring scope from what they happened to mention.
|
|
60
|
-
- **A repeating thing vs. the events it takes part in**: one table can mix a _stable_ thing (a customer, a company) with _repeating_ events (each order, each visit), copying the stable details onto every event row. If that thing genuinely recurs — same customer on many rows — consider a one-row-per-thing table too, linked by id, so "how many distinct X" and the per-X details have clean homes. Lean: split when recurrence is real,
|
|
49
|
+
- **A repeating thing vs. the events it takes part in**: one table can mix a _stable_ thing (a customer, a company) with _repeating_ events (each order, each visit), copying the stable details onto every event row. If that thing genuinely recurs — same customer on many rows — consider a one-row-per-thing table too, linked by id, so "how many distinct X" and the per-X details have clean homes. Lean: split when recurrence is real, one table when each appears once. (Phase 0's one-to-one / one-to-many check tells you which.)
|
|
61
50
|
- **Handling sensitive data** (addresses, emails, phones, IPs, financial details): once you've flagged it (rule 8), _how_ to carry it is user's choice — keep as-is, mask (partial redaction), or drop. Lean: keep what is needed, mask the rest, drop the useless.
|
|
62
51
|
|
|
63
52
|
Phrase a prudential call as a lean plus a nod:
|
|
@@ -70,11 +59,11 @@ Phrase a prudential call as a lean plus a nod:
|
|
|
70
59
|
|
|
71
60
|
### Phase 0 — Get Oriented
|
|
72
61
|
|
|
73
|
-
**Pin down where the data lives — ask before you hunt.** A table or schema name the user mentions tells you _what_ but not _where_: an instance can hold several databases, each with several schemas. Rather than listing them all to find it, just ask — "Which database is this in, and the schema if you know it? No worries if
|
|
62
|
+
**Pin down where the data lives — ask before you hunt.** A table or schema name the user mentions tells you _what_ but not _where_: an instance can hold several databases, each with several schemas. Rather than listing them all to find it, just ask — "Which database is this in, and the schema if you know it? No worries if not — I can find it." A confident answer short-circuits a lot of blind searching; "not sure" costs nothing and you fall back to locating it yourself (use `core`'s narrowest-first crawl ladder). If you've genuinely looked and still can't find a table the user is sure is there, don't keep digging — one likely reason is Metabase hasn't synced that database's latest schema; gently raise it and let the user run the sync from Metabase.
|
|
74
63
|
|
|
75
64
|
As soon as you know which database and schema you're in:
|
|
76
65
|
|
|
77
|
-
- **Show the user the map.** Open the instance's schema map
|
|
66
|
+
- **Show the user the map.** Open the instance's schema map so they can follow along: `<base-url>/data-studio/schema-viewer?database-id=<db-id>&schema=<schema>` — in their browser if you can (`open` / `xdg-open`), else paste the URL. Don't skip this.
|
|
78
67
|
- **Ask for a head start.** "Do you have a picture or file showing how your data fits together, like an ERD?" If yes, read it — it shortcuts the next steps.
|
|
79
68
|
- **Ask for their conventions.** "Is there already cleaned-up data, or a past project, that shows how your team likes this done?" If yes, inspect it: it tells you their naming, their idea of "clean," and existing tables worth linking to.
|
|
80
69
|
|
|
@@ -87,21 +76,21 @@ Orientation done, you're about to go heads-down. First, offer two ways to work:
|
|
|
87
76
|
> - **I dig through it all and bring you a complete plan** to approve before I build anything — quieter; you won't hear much until it's ready.
|
|
88
77
|
> - **We work it out together** — I share what I find and we make the calls as we go.
|
|
89
78
|
|
|
90
|
-
First path: **enter plan mode** (`EnterPlanMode`). Everything up to the agreed table list — investigate, present, prudential calls, naming (Phases 1–3) — happens inside it, read-only; you exit once, at the approval gate before building (Phase 4). Second path: skip it, shape it conversationally through the same phases. Either way, don't build until the design is settled and
|
|
79
|
+
First path: **enter plan mode** (`EnterPlanMode`). Everything up to the agreed table list — investigate, present, prudential calls, naming (Phases 1–3) — happens inside it, read-only; you exit once, at the approval gate before building (Phase 4). Second path: skip it, shape it conversationally through the same phases. Either way, don't build until the design is settled and approved.
|
|
91
80
|
|
|
92
|
-
Plan mode is a long quiet stretch
|
|
81
|
+
Plan mode is a long quiet stretch. So whenever you surface — a question now, the plan at the end — **carry your own context**: recap what it rests on right before you ask, never a back-reference to something said while they were away. And whenever you ask a question, **wait for their answer** — they may say "go" and come back later.
|
|
93
82
|
|
|
94
83
|
Then dig in. Don't narrate this — a single "Let me take a look at what's in here — one minute" is enough. Keep it cheap: never pull whole-warehouse rollups (they blow up); use compact column listings, `LIMIT`/sample queries, and `GROUP BY count(*)`.
|
|
95
84
|
|
|
96
85
|
1. **Map the tables.** List them; pull each one's column names and types; note its own id.
|
|
97
|
-
2. **Find the decode tables.** Normalized SaaS data hides meaning in lookups — `*_field`, `*_field_choice`, `*_question`, `*_choice`, `*_type`. A column like `doodad_4471` is meaningless until you join the lookup and find it's _"Preferred vehicular transport"_. Build that code → label map yourself
|
|
98
|
-
3. **Prove the connections — don't trust declared keys.** Synced databases usually have none. If that's the case, ask the user if they have ERD or relationship information (screenshot, JSON, documentation, etc.). For each `<x>_id`, guess it points at `<x>`, then check what fraction of values actually match the target's id: high = real link, low = decoy, discard. Note one-to-one vs one-to-many. **Also look outward** — does a thing you're about to build already exist as clean data elsewhere
|
|
86
|
+
2. **Find the decode tables.** Normalized SaaS data hides meaning in lookups — `*_field`, `*_field_choice`, `*_question`, `*_choice`, `*_type`. A column like `doodad_4471` is meaningless until you join the lookup and find it's _"Preferred vehicular transport"_. Build that code → label map yourself — never hand the user a coded column and ask what it means — before showing them anything.
|
|
87
|
+
3. **Prove the connections — don't trust declared keys.** Synced databases usually have none. If that's the case, ask the user if they have ERD or relationship information (screenshot, JSON, documentation, etc.). For each `<x>_id`, guess it points at `<x>`, then check what fraction of values actually match the target's id: high = real link, low = decoy, discard. Note one-to-one vs one-to-many. **Also look outward** — does a thing you're about to build already exist as clean data elsewhere (an existing customers table, a product list)? If so, plan to _link_ to it, not duplicate it.
|
|
99
88
|
4. **Pin down "one row per what."** Count rows; check the id is unique; figure out what a single row is. **Watch for lies:** a stale count column, or a table that looks like "all of X" but is a filtered subset.
|
|
100
|
-
5. **Reconcile across related tables.** Do child rows all link to a parent? Orphans? Is one table a trimmed snapshot while another keeps everything? These mismatches matter and the user can't see them
|
|
89
|
+
5. **Reconcile across related tables.** Do child rows all link to a parent? Orphans? Is one table a trimmed snapshot while another keeps everything? These mismatches matter and the user can't see them.
|
|
101
90
|
6. **Profile the values.** List distinct values for coded/low-variety columns; check how full (% non-empty) any column you might drop is; spot multi-valued JSON fields. Profile with the cleaning checklist (end of file) in mind — surface the quality smells you hit, don't silently fix them.
|
|
102
91
|
7. **Cluster into things.** Group tables and columns into the real-world things they describe — a thing may span several tables (one _customer_ across a main table + a loyalty table + custom-profile columns). Decide "one row per \_\_\_" for each and gather its attributes, decoded. Watch for a table that secretly mixes _two_ things — a stable thing plus its repeating events; that's the split in the prudential calls above.
|
|
103
92
|
|
|
104
|
-
**Then, still quietly, sketch the design space.** Once the things and
|
|
93
|
+
**Then, still quietly, sketch the design space.** Once the things and their connections are pinned, brainstorm the questions this data could answer — finance views, leaderboards, breakdowns. **Don't show it or build any of it.** It only pressure-tests your design: would a reasonable pivot to a nearby question force a rewrite? When keeping a column or finer grain _cheaply_ preserves that flexibility, keep it — don't scope so tightly that the next question means starting over.
|
|
105
94
|
|
|
106
95
|
### Phase 2 — Present what you found (plain language)
|
|
107
96
|
|
|
@@ -111,45 +100,52 @@ Three things, in order:
|
|
|
111
100
|
|
|
112
101
|
> **Customers** — one row per customer. Who they are (name, company, location), how they've been in touch, what they've spent, whether they're active or churned.
|
|
113
102
|
|
|
114
|
-
**(b) The full inventory — including what you'd leave out.** Never infer scope silently:
|
|
103
|
+
**(b) The full inventory — including what you'd leave out.** Never infer scope silently (rule 5):
|
|
115
104
|
|
|
116
105
|
> I found 6 kinds of things: **Customers, Orders, Products, Suppliers, Shipments, Returns.** I'd build the first four. **Shipments** and **Returns** also have real data — want those in, or leave them?
|
|
117
106
|
|
|
118
|
-
**(c) What would be set aside — proactively, ranked, two buckets
|
|
107
|
+
**(c) What would be set aside.** This is rule 3 made concrete — proactively, ranked by how much is extant, in two buckets:
|
|
119
108
|
|
|
120
109
|
> Nothing important is lost. A few things set aside:
|
|
121
110
|
> • **Real data** — gift-message text (6 of 10 orders), delivery instructions (most), preferred carrier. Minor, but real — want any kept?
|
|
122
111
|
> • **Safe to drop** — duplicate product names in other languages, internal bookkeeping columns. No real loss.
|
|
123
112
|
|
|
124
|
-
If you spotted existing clean data to link to (step 3), raise it here too —
|
|
113
|
+
If you spotted existing clean data to link to (step 3), raise it here too — **always run a suspected match past the user before wiring it, never graft on silently.** Then ask your prudential questions, one at a time.
|
|
125
114
|
|
|
126
115
|
### Phase 3 — Iterate
|
|
127
116
|
|
|
128
|
-
Cheap, because nothing's built. Adjust the set of things, what's kept, and the shape of any multi-valued pieces until the user's happy. **Agree on what each table will be called** — propose a clear name for each (matching any naming pattern you found in their existing data, Phase 0) and let them adjust. Confirm each name is free — not already an existing table or another transform's output (rule 9) — so building can't overwrite anyone's data.
|
|
117
|
+
Cheap, because nothing's built. Adjust the set of things, what's kept, and the shape of any multi-valued pieces until the user's happy. **Agree on what each table will be called** — propose a clear name for each (matching any naming pattern you found in their existing data, Phase 0) and let them adjust. Confirm each name is free — not already an existing table or another transform's output (rule 9) — so building can't overwrite anyone's data. The name you agree on is the one you build and keep. Re-confirm the final picture in one short recap. **In plan mode, that recap _is_ your exit:** present it as the plan and call `ExitPlanMode` — approval here is the single go-ahead to build. (Iterating together? The recap is just your check before building.)
|
|
129
118
|
|
|
130
119
|
### Phase 4 — Build, check, hand back
|
|
131
120
|
|
|
132
|
-
Design settled — now you build, the first step that writes; plan mode, if you used it, is behind you. Build one wide transform per agreed thing —
|
|
121
|
+
Design settled — now you build, the first step that writes; plan mode, if you used it, is behind you. Build one wide transform per agreed thing (transform body shape, create, run-with-wait — see `transform`), for how it'll be judged: output that's readable on sight, not just one that runs clean. Each table:
|
|
133
122
|
|
|
134
123
|
- **Denormalized, but the link stays.** Copy in related context so casual reading needs no lookups (a product's name and price on the orders table) — **and keep the linking id beside it** (the product's id too, per rule 6). Use the same id name everywhere a thing appears.
|
|
135
|
-
- **Decoded**: codes and JSON become readable text; bookkeeping columns and soft-deleted rows are gone (filter the source's soft-delete flag — Fivetran's `_fivetran_deleted`, Airbyte's `_ab_cdc_deleted_at`, or a plain `deleted_at`/`is_deleted` — so tombstones never reach clean data
|
|
124
|
+
- **Decoded**: codes and JSON become readable text; bookkeeping columns and soft-deleted rows are gone (filter the source's soft-delete flag — Fivetran's `_fivetran_deleted`, Airbyte's `_ab_cdc_deleted_at`, or a plain `deleted_at`/`is_deleted` — so tombstones never reach clean data).
|
|
136
125
|
- **Clean, plain column names**, consistent across tables.
|
|
137
126
|
- **Multi-valued pieces** in the agreed filterable structure (rule 1).
|
|
138
127
|
- **Keep the detail; don't pre-summarize it away.** Build the detailed rows (one per order, one per payment), not pre-computed totals. A convenience count is fine _beside_ the rows, never _instead of_ them — a frozen total only ever answers the one question it was summed for.
|
|
139
128
|
|
|
140
129
|
Then make the links real, not just implied:
|
|
141
130
|
|
|
142
|
-
- **Wire foreign keys between your tables.** Mark each linking id as a foreign key pointing at the id it references
|
|
131
|
+
- **Wire foreign keys between your tables.** Mark each linking id as a foreign key pointing at the id it references — set the column's type to foreign-key and its target so Metabase itself knows the tables connect and can traverse them.
|
|
143
132
|
- **Graft onto existing clean data** the user approved (step 3 / Phase 1): point the linking id at the existing table's id the same way. Link, don't duplicate.
|
|
144
|
-
- **Write down what you learned.** You decoded every column's real meaning while investigating — save it: set a short description on each table and its non-obvious columns (`mb table update` / `mb field update`). The cleaned data then explains itself inside Metabase — in search, in the Question editor, to Metabot — instead of the knowledge living only in this chat.
|
|
145
133
|
|
|
146
|
-
|
|
134
|
+
**Set the metadata — a transform's output starts blank, and these tables are Library-bound.** A fresh transform table has no descriptions, raw column names, and untyped columns. You worked it out while investigating; don't leave that knowledge stranded in this chat. Set it on the table so the data explains itself inside Metabase (search, the Question editor, Metabot) and is fit to publish. The mechanics — `mb field update` for semantic types / FK targets / display names, `mb table update` for table descriptions — and their footguns are in `core`; the calls about _what_ to set:
|
|
135
|
+
|
|
136
|
+
- **Semantic types — the highest-value piece.** A column's semantic type is what makes Metabase treat it right: `type/Email`, `type/Currency`/`type/Price`, `type/Category` (turns into a filter dropdown), `type/City`/`type/State`/`type/Country`, `type/CreationTimestamp`, `type/Description`. Set it on every column whose meaning you decoded. A typed column shows money as money, offers a filter dropdown, and lands on the right chart axis for everyone downstream; an untyped one is a guess.
|
|
137
|
+
- **Descriptions.** A one-line description on each table and every non-obvious column.
|
|
138
|
+
- **Display names.** When a cleaned-up column name still isn't plain English, set a readable `display_name`.
|
|
139
|
+
|
|
140
|
+
When the semantics are **already spelled out** — the user is porting dbt models (the `schema.yml` carries column descriptions and types), or you settled each field's meaning together here — that documentation _is_ the metadata. Carry it straight onto the tables and fields rather than letting it evaporate.
|
|
141
|
+
|
|
142
|
+
When refining a built transform _with_ the user, open its inspector so you're looking at the same thing — `<base-url>/data-studio/transforms/<transform-id>/inspect` — in their browser if you can, else paste the URL. Iterate with `transform update`, never delete-and-recreate.
|
|
147
143
|
|
|
148
144
|
**Check the output before handing back — the user can't.** Two passes, in order.
|
|
149
145
|
|
|
150
146
|
**Pass 1 — Correctness (did it run right).** After each transform runs, run quick ad-hoc tests against what Phase 0 led you to expect: row counts in the right ballpark, decoded columns readable (no stray codes), linking ids that resolve to the other tables, no column unexpectedly all-null or blown up in count. Treat surprises as bugs to chase, not noise. A table that can't combine with the others — a dropped id, or the same id named two ways — is a silent failure; catch it here.
|
|
151
147
|
|
|
152
|
-
**Pass 2 — Fitness (is it nice to use).** Correct isn't the bar; _usable_ is. `SELECT * FROM <table> LIMIT 20` and read every column
|
|
148
|
+
**Pass 2 — Fitness (is it nice to use).** Correct isn't the bar; _usable_ is. `SELECT * FROM <table> LIMIT 20` and read every column as if you'd never seen the source: would a business reader find each one readable? Smells that say not-yet, even though nothing errored:
|
|
153
149
|
|
|
154
150
|
- a multi-valued column still a raw JSON/array blob or `["Email","SMS"]` text — rule 1 never actually got resolved;
|
|
155
151
|
- decoded answers still carrying raw ids with no readable label, or one cryptic column per code;
|
|
@@ -167,7 +163,9 @@ Then report plainly:
|
|
|
167
163
|
>
|
|
168
164
|
> How they connect: each **Order** belongs to a **Customer**; each **Order** lists one or more **Products**.
|
|
169
165
|
|
|
170
|
-
End on that connection map: it's what the user reads to trust the result, and what lets whatever they build next join
|
|
166
|
+
End on that connection map: it's what the user reads to trust the result, and what lets whatever they build next join on the right ids instead of guessing.
|
|
167
|
+
|
|
168
|
+
These clean tables are exactly what belongs in the **Library** — published tables appear first when anyone picks a data source, so people start from your curated set, not the raw source. If the user wants that, publish the polished tables to the Library (`mb library publish` / `mb library create` mechanics, premium feature, and permissions are in `core`). Defining reusable segments / measures / metrics on top is the **reusable-definitions** stage (`references/reusable-definitions.md` in this skill).
|
|
171
169
|
|
|
172
170
|
---
|
|
173
171
|
|
|
@@ -178,7 +176,7 @@ The shape recurs across SaaS exports, whatever the domain. A coded column — sa
|
|
|
178
176
|
Always decode _before_ presenting, so the user sees "Preferred contact method", never `c_4471`. Three cautions:
|
|
179
177
|
|
|
180
178
|
- **Pull the readable name from the lookup, don't type it in.** The label (and any question text) should come _from_ the lookup's `name`, sourced in the query — not pasted as a literal. A hard-typed label goes wrong the moment the source changes.
|
|
181
|
-
- **Codes are usually specific to today's data.** `c_4471` exists only for _this_ form or instance, so one-column-per-code is tied to the data as it stands — a new form
|
|
179
|
+
- **Codes are usually specific to today's data.** `c_4471` exists only for _this_ form or instance, so one-column-per-code is tied to the data as it stands — a new form won't line up. When that's unavoidable, say so on hand-back ("reflects the current form; new questions need a refresh"), and with many such codes prefer the companion-table shape (one row per answer, question text from the lookup): nothing hard-typed, and adding a question is a smaller change.
|
|
182
180
|
- **Normalize encodings once.** Turn raw representations clean in the table itself, so nothing downstream re-derives them: signed amounts → clear positive numbers by kind, 0/1 → true/false, timestamps → one consistent timezone, text → trimmed and case-consistent, and junk placeholders (`"NULL"`, `"N/A"`, `"-"`, empty string) → real null.
|
|
183
181
|
|
|
184
182
|
---
|
|
@@ -197,4 +195,4 @@ A scan-list, not a pipeline — and the governing rule is **surface what you fin
|
|
|
197
195
|
- **Missing data** — random vs. systematic? Surface the pattern; never silently impute or default.
|
|
198
196
|
- **Free text / mixed encodings** — handle the safe parts, flag the rest.
|
|
199
197
|
|
|
200
|
-
|
|
198
|
+
Covered by the rules above, listed to stay on your radar: structural reshaping (decode/JSON/multi-value), orphans & key validity (Phase 0 step 5 + the post-run check), filtering soft-deletes & dropping bookkeeping columns (Phase 4's **Decoded** step), recording meanings (the descriptions step).
|
package/skill-data/{semantic-layer/SKILL.md → data-workflow/references/reusable-definitions.md}
RENAMED
|
@@ -1,73 +1,47 @@
|
|
|
1
|
-
|
|
2
|
-
name: semantic-layer
|
|
3
|
-
description: Turn clean, analysis-ready tables into a shared vocabulary the org reuses - Metabase segments (saved filters, e.g. active customers), measures (saved calculations, e.g. net revenue), and metrics (official numbers, e.g. monthly recurring revenue) - so people stop reinventing the same definition five ways. Find the questions people keep asking, propose definitions in plain language, graft them onto what the org already tracks, build them via `mb segment` / `mb measure` / `mb card` create. For a non-technical user who knows their domain. Load when someone wants to "make this reusable", "define X officially", "standardize how we calculate Y", or "create a segment / measure / metric". Strategy skill for designing reusable definitions; for raw `mb segment` / `mb measure` mechanics, use `core`.
|
|
4
|
-
allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
5
|
-
---
|
|
1
|
+
# Define reusable metrics
|
|
6
2
|
|
|
7
|
-
|
|
3
|
+
> Part of the **`data-workflow`** skill — the "define reusable metrics" stage. It assumes that skill's **Shared Contract** (how to communicate, PII, autonomy, permission-denied) and final-recap rule. CLI mechanics: `core` (the `segment`/`measure` verbs, `revision_message`, library publish), `mbql` (definition bodies).
|
|
8
4
|
|
|
9
|
-
|
|
5
|
+
- [Autonomy applied here](#autonomy-applied-here)
|
|
6
|
+
- [Two kinds of decisions](#two-kinds-of-decisions)
|
|
7
|
+
- [The process](#the-process) — Phases 0–3
|
|
8
|
+
- [A worked example](#a-worked-example-for-your-reference-not-the-users)
|
|
10
9
|
|
|
11
10
|
Your job: take the clean, analysis-ready tables that already exist and turn the **questions people keep asking** into **shared, reusable definitions** — so "active customer", "net revenue", and "monthly recurring revenue" mean one thing across the whole organization, not five slightly-different things in five people's saved questions.
|
|
12
11
|
|
|
13
12
|
You build three kinds of reusable thing. These are real Metabase features with real names — **use the Metabase names** (segment, measure, metric) and teach them to the user as you go. They're product vocabulary, not jargon. Pair the name with a plain gloss the first time, then use it freely:
|
|
14
13
|
|
|
15
14
|
- **Segment** — a saved filter on a table. A reusable row-selector: "Active customers", "orders over $100", "EU shipments". People pick it from the **Filter** block in the query builder instead of re-typing the conditions. (Docs: <https://www.metabase.com/docs/latest/data-studio/segments>.)
|
|
16
|
-
- **Measure** — a saved aggregation on a table. A reusable calculation: "Net Promoter Score", "average order value". People pick it from the **Summarize** block instead of re-writing the formula.
|
|
15
|
+
- **Measure** — a saved aggregation on a table. A reusable calculation: "Net Promoter Score", "average order value". People pick it from the **Summarize** block instead of re-writing the formula. (Docs: <https://www.metabase.com/docs/latest/data-studio/measures>.)
|
|
17
16
|
- **Metric** — a reusable aggregation that lives in a **collection** (a folder), not bolted to a table. "Monthly recurring revenue", "weekly active users". It's the org's official definition of an important number, can be saved into the **Library**, and can carry a default time dimension for charting. (Docs: <https://www.metabase.com/docs/latest/data-modeling/metrics>.)
|
|
18
17
|
|
|
19
18
|
Introduce each like: _"I'll save this as a **segment** — that's Metabase's word for a reusable filter, so you can pull up active customers with one click anytime."_ After that, just say "segment".
|
|
20
19
|
|
|
21
|
-
This
|
|
20
|
+
This stage runs **after** the analysis-ready tables exist (make the table wider first — the **build-clean-tables** stage, `references/building-clean-tables.md`; the `transform` skill has the mechanics). Segments and measures only reach one table (hard rule 4 below), so a semantic layer on raw, normalized tables is nearly useless: a real answer rarely lives in a single raw table. **Wide clean tables first, segments/measures/metrics second.**
|
|
22
21
|
|
|
23
|
-
|
|
22
|
+
Load the CLI skills you'll need — `mb skills get core` (auth, profiles, inspection, the `segment`/`measure`/`library` verb mechanics) and `mb skills get mbql` (the definition bodies). Auth and scratch files follow `core`'s recipe: resolve the profile and carry `--profile <name>` into every command.
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
mb skills get core # auth, profiles, db/table/field inspection, query, search
|
|
27
|
-
mb skills get mbql # the definition bodies (filters and aggregations) are MBQL 5
|
|
28
|
-
```
|
|
24
|
+
## Autonomy applied here
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
## Who you're talking to
|
|
35
|
-
|
|
36
|
-
A **non-technical user who knows their domain well.** They know the business — who an "active" customer is, what counts as "revenue" — but not databases. So:
|
|
37
|
-
|
|
38
|
-
- **Teach the words a curious non-engineer can follow; skip the deep-internals jargon.** Two sets are fine and worth teaching: Metabase product terms (**segment, measure, metric, collection, Library, the Filter / Summarize blocks**) and common data words a domain user can reasonably learn (**table, column, foreign key, schema, join, filter, row**) — gloss them once, then use them. Avoid **deep-internals jargon** that buys nothing for this user: grain, cardinality, normalize/denormalize, surrogate key, MBQL, `table_id`, materialize. Prefer the plain effect when it's clearer ("this number needs data from two tables" reads easier than "this needs a join across two fact tables") — but you don't have to contort around "foreign key" or "schema".
|
|
39
|
-
- **Talk about the question, then name the object.** Lead with what it does for them, then attach the term: _"I'll save 'big orders' as a segment so you can pull them up with one click."_ Not a bare "I'll create a segment on `table_id` 235."
|
|
40
|
-
- **Be a helpful colleague, not an engineer reporting status.** Elide the wiring (ids, query bodies, the CLI). Ask the one question that actually matters.
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## Autonomy — honor the mode the user set
|
|
45
|
-
|
|
46
|
-
The user already picked an autonomy mode (the router's Shared Contract asks the slider once, up front — don't re-ask). Apply it to building definitions:
|
|
26
|
+
The user already set an autonomy mode (the `data-workflow` autonomy slider — don't re-ask, don't redefine it). How it lands on building definitions:
|
|
47
27
|
|
|
48
28
|
| Mode | What you do |
|
|
49
29
|
| ----------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
50
30
|
| **Check on everything** | Confirm every single definition (name + plain description) before building it. |
|
|
51
|
-
| **Balanced** (default) | Build the obvious ones; ask only on the judgment calls (the prudential list
|
|
31
|
+
| **Balanced** (default) | Build the obvious ones; ask only on the judgment calls (the prudential list) and anything ambiguous. |
|
|
52
32
|
| **Just go** | Build the whole set, surface judgment calls as "here's what I picked and why — say the word to change any." |
|
|
53
33
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
1. **When you're genuinely unsure — ask. Never assume.** "Just go" means _decide the obvious_, not _guess on the unclear_. A wrong-but-confident definition of "active customer" is worse than a one-line question.
|
|
57
|
-
2. **The final gate is a hard stop (see Phase 3).** No mode auto-publishes. You always stop, recap in plain language, and hand the user something to eyeball before anything goes live.
|
|
58
|
-
|
|
59
|
-
---
|
|
34
|
+
Two things never bend in any mode: when genuinely unsure, **ask** (the Shared Contract's rule — "Just go" means decide the obvious, not guess on the unclear); and the final gate is a **hard stop** (Phase 3) — no mode auto-publishes.
|
|
60
35
|
|
|
61
36
|
## Two kinds of decisions
|
|
62
37
|
|
|
63
38
|
**Hard rules — absolutes, never ask:**
|
|
64
39
|
|
|
65
40
|
1. **Never invent what a word means — pin it to real data.** "Active customer" is not yours to define. Before you build a segment for it, find out (from the user, or from how the data actually behaves) what _they_ mean: ordered in the last 90 days? Has a live subscription? Logged in this month? Confirm against actual values, then build to that. A definition built on a guessed meaning is a silent lie everyone then trusts.
|
|
66
|
-
2. **Keep the language at the level
|
|
41
|
+
2. **Keep the language at the level the Shared Contract sets.** Metabase terms and common data words (table, column, foreign key, schema, join) are fine and worth teaching; deep-internals jargon (grain, cardinality, surrogate key, `table_id`) is not.
|
|
67
42
|
3. **Don't bury filters inside measures.** A measure should aggregate _what it's given_; let the user combine it with a segment at question time, rather than welding a filter into the measure. Welded-in filters collide and confuse when someone applies their own filter on top — and the metrics doc explicitly recommends against it. (Use conditional forms like `SumIf`/`CountIf` for "sum only the paid ones" — that's part of the measure's formula, not a hidden row filter.)
|
|
68
|
-
4. **Respect where each thing can reach.** Segments and measures work **only** on a question built _directly_ on their own table — not through a join, not on a question-built-on-a-question (the Limitations sections of both docs say so).
|
|
69
|
-
5. **
|
|
70
|
-
6. **Every definition keeps a clear, plain name and a one-line description in the user's words.** The name is what they'll see in a menu six weeks from now with no memory of this conversation. "Active customers (ordered in last 90 days)" beats "active_seg_v2".
|
|
43
|
+
4. **Respect where each thing can reach (single-table reach).** Segments and measures work **only** on a question built _directly_ on their own table — not through a join, not on a question-built-on-a-question (the Limitations sections of both docs say so). A metric is data-source-bound the same way: defined on table X, it appears only on questions built on table X, not on anything derived from it. If a definition needs more than one table's worth of data, you do **not** force a join into it — you make the table wider first (the **build-clean-tables** stage, `references/building-clean-tables.md`; the `transform` skill has the mechanics), then define on that. Quietly building a segment/measure/metric that silently won't show up where the user expects is a hard-rule violation.
|
|
44
|
+
5. **Every definition keeps a clear, plain name and a one-line description in the user's words.** The name is what they'll see in a menu six weeks from now with no memory of this conversation. "Active customers (ordered in last 90 days)" beats "active_seg_v2".
|
|
71
45
|
|
|
72
46
|
**Prudential calls — genuinely contextual, state your lean, let the user decide** (skip the ask in "Just go" mode — pick your lean, flag it):
|
|
73
47
|
|
|
@@ -76,6 +50,7 @@ The user already picked an autonomy mode (the router's Shared Contract asks the
|
|
|
76
50
|
- "Let me add up revenue the same way everywhere, on this table" → a **measure** on the table.
|
|
77
51
|
- "Revenue is an _official company number_ people pull onto dashboards" → a **metric** in a collection, with a default month-by-month view so it charts cleanly. Lean: make it a metric when it's a headline figure the org reuses across many questions/dashboards; keep it a measure when it's a table-local convenience.
|
|
78
52
|
- **Where the metric lives.** Metrics sit in a collection (folder). Lean: put the org's blessed ones in the shared **Library** so they surface prominently; keep experimental ones in a working collection until trusted.
|
|
53
|
+
- **Publish the official tables to the Library.** The clean, analysis-ready tables your definitions sit on are the org's official starting points — the **Library** is how you mark them as such. Tables published to the Library's **Data** section appear _first_ when anyone picks a data source, nudging people toward your curated tables instead of raw warehouse ones. Lean: publish the wide clean tables you built the semantic layer on; hold back raw or half-built ones. Surface which tables you'd publish and confirm. (Library is a Pro/Enterprise feature; only admins and data analysts can publish — mechanics in `core`.)
|
|
79
54
|
- **Default time dimension for a metric.** A monthly default makes it chart nicely on a dashboard, but doesn't lock anyone out of other groupings. Lean: set a sensible default (usually month) for anything headline; leave it off for raw counts that aren't inherently time-series.
|
|
80
55
|
- **How strict a segment is.** "Active" = last 30 vs 90 days is a real business call with no right answer from the data alone. Lean: surface the few reasonable thresholds with how many rows each catches, let the user pick.
|
|
81
56
|
|
|
@@ -83,8 +58,6 @@ Phrase a prudential call as a lean plus a nod:
|
|
|
83
58
|
|
|
84
59
|
> "I'd save 'revenue' as a metric — Metabase's term for an official, reusable number — rather than a table-only measure, since people pull it onto dashboards a lot. Good?"
|
|
85
60
|
|
|
86
|
-
---
|
|
87
|
-
|
|
88
61
|
## The process
|
|
89
62
|
|
|
90
63
|
### Phase 0 — Understand what's reusable (quietly)
|
|
@@ -93,9 +66,9 @@ Don't narrate. One "Let me see what's here and how people are already slicing it
|
|
|
93
66
|
|
|
94
67
|
1. **Confirm the analysis-ready tables exist.** List tables; find the wide, clean ones (a transform step's output). If the user is pointing you at raw normalized tables, say so plainly and suggest building the clean table first — don't build a hobbled semantic layer on raw data.
|
|
95
68
|
2. **Find the questions people keep asking.** Search existing saved questions and dashboards (`mb search`, `mb card list`) for repeated filters and repeated calculations — the same "status = active" written eleven times, five hand-rolled versions of revenue. Those repeats _are_ the semantic layer waiting to be named. This is the highest-signal input; mine it before proposing anything.
|
|
96
|
-
3. **Learn the real meanings.** For every candidate segment ("active", "churned", "high-value"), find what the words map to in actual values — distinct values of a status column, the spread of an amount column.
|
|
69
|
+
3. **Learn the real meanings.** For every candidate segment ("active", "churned", "high-value"), find what the words map to in actual values — distinct values of a status column, the spread of an amount column. Pin every definition to real data (hard rule 1).
|
|
97
70
|
4. **Graft onto what the org already tracks.** This is the part a model does worst and a human does best, so lean on the user: a new definition is far more useful when it lines up with the entities and language the organization _already_ uses. Before inventing "customer health score", ask whether there's already a notion of an active/at-risk customer in their world, and match it. Isolated definitions that don't connect to the existing model are low-value. Ask; don't infer the connection from column names.
|
|
98
|
-
5. **Check reach before promising.** For each candidate, confirm it can actually live where it needs to: a single-table segment/measure must sit on the table people will build questions on; a multi-table answer needs a wider table first (hard
|
|
71
|
+
5. **Check reach before promising.** For each candidate, confirm it can actually live where it needs to: a single-table segment/measure must sit on the table people will build questions on; a multi-table answer needs a wider table first (hard rule 4). Catch this now, not after building something that won't appear.
|
|
99
72
|
|
|
100
73
|
### Phase 1 — Propose the shared vocabulary (plain language)
|
|
101
74
|
|
|
@@ -119,15 +92,16 @@ Then surface what you're _not_ saving and why ("I left 'orders this week' alone
|
|
|
119
92
|
|
|
120
93
|
### Phase 2 — Iterate (cheap, nothing built yet)
|
|
121
94
|
|
|
122
|
-
Adjust names, meanings, thresholds, and which-kind-of-thing until the user is happy. Re-confirm the final list in one short recap. If a definition turns out to need more than one table, say so plainly and point back to making the table wider — don't smuggle in a join.
|
|
95
|
+
Adjust names, meanings, thresholds, and which-kind-of-thing until the user is happy. Re-confirm the final list in one short recap. If a definition turns out to need more than one table, say so plainly and point back to making the table wider (hard rule 4) — don't smuggle in a join.
|
|
123
96
|
|
|
124
97
|
### Phase 3 — Build, verify quietly, then hard-stop
|
|
125
98
|
|
|
126
|
-
Build each agreed definition.
|
|
99
|
+
Build each agreed definition. The verb mechanics (create/update flags, the `revision_message` audit-note rule on `update`, never delete-and-recreate) live in `core`; the definition bodies live in `mbql`:
|
|
127
100
|
|
|
128
|
-
- **Segment** → `mb segment create`.
|
|
129
|
-
- **Measure** → `mb measure create`.
|
|
130
|
-
- **Metric** → `mb card create` with the metric shape (`type: "metric"`) — it lives in a **collection**, carries
|
|
101
|
+
- **Segment** → `mb segment create`. A flat MBQL filter clause on a table.
|
|
102
|
+
- **Measure** → `mb measure create`. **Exactly one** aggregation on a table.
|
|
103
|
+
- **Metric** → `mb card create` with the metric shape (`type: "metric"`) — it lives in a **collection**, carries the aggregation plus an optional default time dimension. Put org-blessed ones in the Library collection.
|
|
104
|
+
- **Publish the official tables** → `mb library create` then `mb library publish` (mechanics in `core`) to move the clean tables your definitions sit on into the Library's **Data** section, so people start from your curated set, not raw warehouse tables.
|
|
131
105
|
|
|
132
106
|
Then **verify what the user can't see**, before you hand back:
|
|
133
107
|
|
|
@@ -149,18 +123,19 @@ Then **stop. Hard gate — every mode, no exceptions.** Recap in plain language
|
|
|
149
123
|
> **Metric** (in your **Library**, charts by month):
|
|
150
124
|
> • **Monthly recurring revenue**
|
|
151
125
|
>
|
|
126
|
+
> **Published to the Library** (these now show up first when anyone picks a data source):
|
|
127
|
+
> • **Customers**, **Orders**
|
|
128
|
+
>
|
|
152
129
|
> Open any of those tables' Filter or Summarize block in Metabase to see them in place and try one — give it a look before you start building dashboards on top.
|
|
153
130
|
|
|
154
131
|
End on that plain-language map. It's what the user reads to trust the result — and it's what stops a wrong definition from quietly propagating into everything built next.
|
|
155
132
|
|
|
156
|
-
---
|
|
157
|
-
|
|
158
133
|
## A worked example (for your reference, not the user's)
|
|
159
134
|
|
|
160
135
|
User: _"Everyone calculates 'active users' differently — can you make it official?"_
|
|
161
136
|
|
|
162
137
|
- **Don't** create a segment from the phrase alone. **Find the real meaning first:** search existing questions — three people filter on "last seen in the last 30 days", two on "subscription status = active". That's the ambiguity to resolve. Ask: "I see two takes on 'active' — seen in the last 30 days, or has a live subscription. Which do you mean?" (hard rule 1).
|
|
163
|
-
- They say "live subscription, and seen in the last 30 days." **Check reach:** both pieces of info must live on the one table people build questions on. If subscription status and last-seen sit on two different tables, a single segment can't span them (hard rule 4) — to the user: "those two facts live in different places right now, so I'll widen your Customers table to carry both first, then save the filter on it."
|
|
138
|
+
- They say "live subscription, and seen in the last 30 days." **Check reach:** both pieces of info must live on the one table people build questions on. If subscription status and last-seen sit on two different tables, a single segment can't span them (hard rule 4) — to the user: "those two facts live in different places right now, so I'll widen your Customers table to carry both first, then save the filter on it." Make the table wider first (the **build-clean-tables** stage, `references/building-clean-tables.md`; the `transform` skill has the mechanics), then the segment on the wide table.
|
|
164
139
|
- Build it as a segment on the wide table. **Verify** the row count is plausible. **Recap** plainly and stop: "Saved **Active users** — live subscription and seen in the last 30 days — as a segment on your Customers table; it's in the Filter block there. Have a look before you build on it."
|
|
165
140
|
|
|
166
141
|
The shape recurs: a word people use loosely → pin it to real values → check it can live where they'll use it → build → verify → hard-stop with a plain recap.
|
|
@@ -6,9 +6,9 @@ allowed-tools: Read, Write, Edit, Bash, AskUserQuestion
|
|
|
6
6
|
|
|
7
7
|
# Documents
|
|
8
8
|
|
|
9
|
-
A **document** is a Metabase rich-text page (a "report" / notebook) that mixes prose with embedded saved questions and links to other Metabase entities. The body is a **TipTap** JSON tree (TipTap is the editor; the wire format is ProseMirror JSON,
|
|
9
|
+
A **document** is a Metabase rich-text page (a "report" / notebook) that mixes prose with embedded saved questions and links to other Metabase entities. The body is a **TipTap** JSON tree (TipTap is the editor; the wire format is ProseMirror JSON, stored under `content_type: "application/json+vnd.prose-mirror"`).
|
|
10
10
|
|
|
11
|
-
This skill covers authoring the body and driving the verbs.
|
|
11
|
+
This skill covers authoring the body and driving the verbs. Flag conventions, body-input precedence, `./.scratch`, and `mb uuid` live in `core` (`mb skills get core`).
|
|
12
12
|
|
|
13
13
|
## Command surface
|
|
14
14
|
|
|
@@ -20,19 +20,15 @@ mb document update <id> --file patch.json --profile <name> --json # PATCH sema
|
|
|
20
20
|
mb document archive <id> --profile <name> --json # soft-delete (PUT archived:true)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
- `list` returns the standard envelope (`{data, returned, total}`). The compact item is `{id, name, collection_id, archived, creator_id, can_write}`
|
|
23
|
+
- `list` returns the standard envelope (`{data, returned, total}`). The compact item is `{id, name, collection_id, archived, creator_id, can_write}` and omits the (potentially huge) `document` body — pull the body with `get --full`.
|
|
24
24
|
- `archive` is the only delete, mirroring `card` / `dashboard`. **Unarchive** with `mb document update <id> --body '{"archived":false}'`.
|
|
25
|
-
- `update` is PATCH — send only the keys you want to change
|
|
25
|
+
- `update` is PATCH — send only the keys you want to change, and only these are accepted: `name`, `document`, `collection_id`, `collection_position`, `archived`. Replacing `document` replaces the **whole** body; there is no partial-node patch.
|
|
26
26
|
|
|
27
27
|
## Node ids (`_id`)
|
|
28
28
|
|
|
29
|
-
The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** and reject a body missing any
|
|
29
|
+
The editor anchors only these node types with an `_id` (a UUID): `paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`. **`create`/`update` require a non-empty `_id` on every node of those types** and reject a body missing any ("did not match expected schema"). Other node types (`doc`, `text`, `listItem`, `resizeNode`, `flexContainer`, …) take no `_id` and are left alone. Without them the editor backfills ids when the document opens, which makes a freshly-saved document show a spurious "unsaved changes" prompt.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
mb uuid --count 5 --json # → ["…", …] one UUID per id-bearing node
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Set each as that node's `attrs._id`. Without them the editor backfills ids when the document opens, which makes a freshly-saved document show a spurious "unsaved changes" prompt.
|
|
31
|
+
Mint the ids with `mb uuid --count <n> --json` (→ `["…", …]`), one per id-bearing node, and set each as that node's `attrs._id`.
|
|
36
32
|
|
|
37
33
|
## Body shape (create / update)
|
|
38
34
|
|
|
@@ -90,7 +86,7 @@ Every node is `{ "type": string, "attrs"?: object, "content"?: [nodes], "text"?:
|
|
|
90
86
|
|
|
91
87
|
## Embedding an existing card
|
|
92
88
|
|
|
93
|
-
A document embedding
|
|
89
|
+
Find the id with `mb card list --profile <name> --json` (or `mb search --models card "<text>"`), then reference it in a `cardEmbed`. A document embedding existing card 114 under a heading (only the id-bearing nodes carry `_id`):
|
|
94
90
|
|
|
95
91
|
```json
|
|
96
92
|
{
|
|
@@ -111,8 +107,6 @@ A document embedding an existing card (id 114) under a heading (only the id-bear
|
|
|
111
107
|
}
|
|
112
108
|
```
|
|
113
109
|
|
|
114
|
-
To embed an existing card, find its id with `mb card list --profile <name> --json` (or `mb search --models card "<text>"`), then reference it in a `cardEmbed`.
|
|
115
|
-
|
|
116
110
|
## Creating brand-new cards inline with the document
|
|
117
111
|
|
|
118
112
|
You can create cards atomically with the document instead of pre-creating them. Reference each new card by a **negative** id in its `cardEmbed.attrs.id`, then supply the card definitions in a top-level `cards` map keyed by the same negative ids. The server creates the real cards and rewrites the negative ids to the real positive ids in the stored body.
|
|
@@ -138,11 +132,11 @@ You can create cards atomically with the document instead of pre-creating them.
|
|
|
138
132
|
}
|
|
139
133
|
```
|
|
140
134
|
|
|
141
|
-
Each entry in `cards` needs at least `{name, dataset_query, display, visualization_settings}` (these are card definitions, not TipTap nodes, so they take no `_id`). Author the `dataset_query` with
|
|
135
|
+
Each entry in `cards` needs at least `{name, dataset_query, display, visualization_settings}` (these are card definitions, not TipTap nodes, so they take no `_id`). Author the `dataset_query` with `mbql` (`mb skills get mbql`) and the `visualization_settings` with `visualization`. For most edits, prefer embedding cards that already exist (a plain positive `id` in `cardEmbed`) — inline creation is for "build the report and its questions in one shot".
|
|
142
136
|
|
|
143
137
|
## Iterating on a document
|
|
144
138
|
|
|
145
|
-
`update` replaces the whole `document` body, so the safe loop is **read → edit → write**. A fetched body already carries `_id`s on its id-bearing nodes
|
|
139
|
+
`update` replaces the whole `document` body, so the safe loop is **read → edit → write**. A fetched body already carries `_id`s on its id-bearing nodes — preserve them, and only mint new ones for id-bearing nodes you add. Don't hand-merge a partial node tree into a live document; pull the current `document`, mutate the array, and PUT the whole thing back.
|
|
146
140
|
|
|
147
141
|
```bash
|
|
148
142
|
mb document get <id> --full --profile <name> --json | jq '.document' > ./.scratch/body.json
|
|
@@ -151,12 +145,8 @@ jq -n --slurpfile d ./.scratch/body.json '{document: $d[0]}' > ./.scratch/patch.
|
|
|
151
145
|
mb document update <id> --file ./.scratch/patch.json --profile <name> --json
|
|
152
146
|
```
|
|
153
147
|
|
|
154
|
-
|
|
148
|
+
To rename without touching the body, patch only `name`: `mb document update <id> --body '{"name":"New title"}'`.
|
|
155
149
|
|
|
156
150
|
## Don't
|
|
157
151
|
|
|
158
|
-
- Don't omit `_id` on an id-bearing node (`paragraph`, `heading`, `codeBlock`, `orderedList`, `bulletList`, `blockquote`, `cardEmbed`, `supportingText`) — `create`/`update` reject the body ("did not match expected schema"). Mint ids with `mb uuid`.
|
|
159
|
-
- Don't paste a whole `document get` response into `update` — `update` only accepts `name`, `document`, `collection_id`, `collection_position`, `archived`. Send the body under the `document` key, not the full record.
|
|
160
|
-
- Don't put the full `document` body in `list` expectations — `list` is compact and omits it by design; use `get --full`.
|
|
161
152
|
- Don't invent node types. Stick to the inventory above; unknown block types render as empty/broken in the editor even though the response schema is lenient.
|
|
162
|
-
- Don't author a card's `dataset_query` or `visualization_settings` from this skill alone — load `mbql` and `viz`.
|