@canonical/pragma-cli 0.35.0 → 0.37.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 (295) hide show
  1. package/README.md +2 -2
  2. package/bundled-skills/add-standard/SKILL.md +338 -0
  3. package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
  4. package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
  5. package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
  6. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
  7. package/bundled-skills/anatomy-author/SKILL.md +1226 -0
  8. package/bundled-skills/design-auditor/SKILL.md +455 -0
  9. package/bundled-skills/implement-component/SKILL.md +333 -0
  10. package/bundled-skills/specify-component/SKILL.md +331 -0
  11. package/bundled-skills/specify-pattern/SKILL.md +292 -0
  12. package/dist/package.json +24 -20
  13. package/dist/pragma.conf.js +266 -34
  14. package/dist/pragma.conf.js.map +1 -1
  15. package/dist/src/bin.js +1 -1
  16. package/dist/src/bin.js.map +1 -1
  17. package/dist/src/capabilities/capabilities/hints.js +1 -1
  18. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  19. package/dist/src/capabilities/colophon/colophon.render.js +1 -1
  20. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  21. package/dist/src/capabilities/create/create.verb.js +2 -1
  22. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  23. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  24. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  25. package/dist/src/capabilities/create/mount.js +4 -3
  26. package/dist/src/capabilities/create/mount.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  28. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  29. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +60 -4
  30. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  31. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +8 -8
  32. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -1
  33. package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
  34. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
  35. package/dist/src/capabilities/doctor/checks/index.js +6 -6
  36. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  37. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  38. package/dist/src/capabilities/doctor/checks/targetHealth.js +194 -50
  39. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  40. package/dist/src/capabilities/doctor/doctor.render.js +43 -64
  41. package/dist/src/capabilities/doctor/doctor.render.js.map +1 -1
  42. package/dist/src/capabilities/doctor/doctor.verb.js +6 -2
  43. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  44. package/dist/src/capabilities/doctor/runChecks.js +14 -11
  45. package/dist/src/capabilities/doctor/runChecks.js.map +1 -1
  46. package/dist/src/capabilities/graph/query.render.js +2 -2
  47. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  48. package/dist/src/capabilities/info/collectInfo.js +8 -2
  49. package/dist/src/capabilities/info/collectInfo.js.map +1 -1
  50. package/dist/src/capabilities/info/info.render.js +6 -2
  51. package/dist/src/capabilities/info/info.render.js.map +1 -1
  52. package/dist/src/capabilities/prompt/prompt.render.js +2 -2
  53. package/dist/src/capabilities/prompt/prompt.render.js.map +1 -1
  54. package/dist/src/capabilities/setup/buildPlan.js +43 -39
  55. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  56. package/dist/src/capabilities/setup/mode.js +2 -2
  57. package/dist/src/capabilities/setup/operations/index.js +4 -4
  58. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  60. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/setupGenerator.js +156 -58
  64. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  65. package/dist/src/capabilities/setup/operations/setupLsp.js +136 -38
  66. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  67. package/dist/src/capabilities/setup/operations/setupMcp.js +101 -52
  68. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  69. package/dist/src/capabilities/setup/operations/setupSkills.js +329 -49
  70. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  71. package/dist/src/capabilities/setup/plan.js.map +1 -1
  72. package/dist/src/capabilities/setup/plan.render.js +144 -66
  73. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  74. package/dist/src/capabilities/setup/setup.render.js +1 -1
  75. package/dist/src/capabilities/setup/setup.verb.js +165 -63
  76. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  77. package/dist/src/capabilities/setup/targets.js +122 -51
  78. package/dist/src/capabilities/setup/targets.js.map +1 -1
  79. package/dist/src/capabilities/shared/index.js +7 -6
  80. package/dist/src/capabilities/shared/index.js.map +1 -1
  81. package/dist/src/capabilities/shared/packageManager.js +300 -54
  82. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  83. package/dist/src/capabilities/skill/discover.js +97 -11
  84. package/dist/src/capabilities/skill/discover.js.map +1 -1
  85. package/dist/src/capabilities/skill/render.js +2 -2
  86. package/dist/src/capabilities/skill/render.js.map +1 -1
  87. package/dist/src/capabilities/sources/installSkills.js +145 -3
  88. package/dist/src/capabilities/sources/installSkills.js.map +1 -1
  89. package/dist/src/capabilities/sources/runUpdate.js +68 -2
  90. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  91. package/dist/src/capabilities/sources/update.verb.js +3 -3
  92. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  93. package/dist/src/capabilities/upgrade/runUpgrade.js +43 -7
  94. package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -1
  95. package/dist/src/capabilities/upgrade/upgrade.render.js +10 -2
  96. package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -1
  97. package/dist/src/constants.js +13 -2
  98. package/dist/src/constants.js.map +1 -1
  99. package/dist/src/kernel/index.js +11 -2
  100. package/dist/src/kernel/index.js.map +1 -1
  101. package/dist/src/kernel/interactivity.js +68 -0
  102. package/dist/src/kernel/interactivity.js.map +1 -0
  103. package/dist/src/kernel/packs/renderPack.js +3 -3
  104. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  105. package/dist/src/kernel/packs/resolveEntity.js +63 -12
  106. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  107. package/dist/src/kernel/packs/runBodies.js +57 -4
  108. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  109. package/dist/src/kernel/packs/schema.js +54 -2
  110. package/dist/src/kernel/packs/schema.js.map +1 -1
  111. package/dist/src/kernel/packs/sparql/applyFilters.js +104 -5
  112. package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -1
  113. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +310 -43
  114. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  115. package/dist/src/kernel/packs/types.js.map +1 -1
  116. package/dist/src/kernel/project/cli/buildProgram.js +1 -1
  117. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  118. package/dist/src/kernel/project/cli/dispatch.js +46 -24
  119. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  120. package/dist/src/kernel/project/cli/globalFlags.js +4 -1
  121. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  122. package/dist/src/kernel/project/cli/helpFormat.js +1 -2
  123. package/dist/src/kernel/project/cli/helpFormat.js.map +1 -1
  124. package/dist/src/kernel/project/cli/rootHelp.js +42 -9
  125. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  126. package/dist/src/kernel/project/mcp/registerVerb.js +16 -1
  127. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  128. package/dist/src/kernel/render/renderers.js +1 -1
  129. package/dist/src/kernel/render/style.js +29 -9
  130. package/dist/src/kernel/render/style.js.map +1 -1
  131. package/dist/src/kernel/render/vocabulary.js +174 -0
  132. package/dist/src/kernel/render/vocabulary.js.map +1 -0
  133. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  134. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  135. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  136. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  137. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  138. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  139. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  140. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  141. package/dist/src/kernel/runtime/graphpack/read.js +14 -9
  142. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  143. package/dist/src/kernel/runtime/loadSession.js +3 -1
  144. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  145. package/dist/src/kernel/runtime/probe.js +1 -1
  146. package/dist/src/kernel/runtime/probe.js.map +1 -1
  147. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  148. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  149. package/dist/src/kernel/runtime/refs/resolve.js +9 -3
  150. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  151. package/dist/src/kernel/spec/emitReference.js +1 -1
  152. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  153. package/package.json +24 -20
  154. package/pragma.conf.ts +272 -34
  155. package/src/bin.ts +8 -1
  156. package/src/capabilities/block.parity.test.ts +25 -11
  157. package/src/capabilities/block.shipped.exec.test.ts +282 -0
  158. package/src/capabilities/block.tierRank.exec.test.ts +300 -0
  159. package/src/capabilities/capabilities/hints.ts +1 -1
  160. package/src/capabilities/colophon/colophon.render.ts +1 -2
  161. package/src/capabilities/colophon/colophon.test.ts +2 -2
  162. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  163. package/src/capabilities/create/byteEquality.test.ts +28 -1
  164. package/src/capabilities/create/create.test.ts +1 -0
  165. package/src/capabilities/create/create.verb.ts +2 -1
  166. package/src/capabilities/create/createGrammar.test.ts +5 -2
  167. package/src/capabilities/create/createSurface.generated.ts +23 -1
  168. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  169. package/src/capabilities/create/mount.ts +3 -3
  170. package/src/capabilities/distribution.parity.test.ts +4 -0
  171. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  172. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  173. package/src/capabilities/doctor/checks/checkPackageRefs.ts +69 -4
  174. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +6 -4
  175. package/src/capabilities/doctor/checks/checkShellCompletions.ts +8 -8
  176. package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
  177. package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
  178. package/src/capabilities/doctor/checks/index.ts +6 -6
  179. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  180. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  181. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  182. package/src/capabilities/doctor/checks/targetHealth.ts +229 -59
  183. package/src/capabilities/doctor/doctor.render.test.ts +108 -32
  184. package/src/capabilities/doctor/doctor.render.ts +56 -80
  185. package/src/capabilities/doctor/doctor.test.ts +247 -35
  186. package/src/capabilities/doctor/doctor.verb.ts +7 -2
  187. package/src/capabilities/doctor/runChecks.ts +14 -11
  188. package/src/capabilities/doctor/types.ts +15 -24
  189. package/src/capabilities/emptyRecovery.test.ts +10 -2
  190. package/src/capabilities/graph/query.render.ts +2 -2
  191. package/src/capabilities/info/collectInfo.ts +8 -1
  192. package/src/capabilities/info/info.render.ts +6 -2
  193. package/src/capabilities/info/info.test.ts +37 -1
  194. package/src/capabilities/info/types.ts +10 -2
  195. package/src/capabilities/lazy.test.ts +26 -2
  196. package/src/capabilities/listLookup.shipped.exec.test.ts +340 -0
  197. package/src/capabilities/prompt/prompt.render.ts +2 -2
  198. package/src/capabilities/renderVocabularySync.test.ts +82 -0
  199. package/src/capabilities/resources/resources.test.ts +38 -2
  200. package/src/capabilities/scopeTypeSync.test.ts +29 -15
  201. package/src/capabilities/setup/buildPlan.ts +45 -41
  202. package/src/capabilities/setup/index.ts +1 -1
  203. package/src/capabilities/setup/mode.ts +2 -2
  204. package/src/capabilities/setup/operations/index.ts +6 -1
  205. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  206. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  207. package/src/capabilities/setup/operations/setupGenerator.ts +227 -84
  208. package/src/capabilities/setup/operations/setupLsp.ts +203 -38
  209. package/src/capabilities/setup/operations/setupMcp.ts +139 -62
  210. package/src/capabilities/setup/operations/setupSkills.ts +399 -60
  211. package/src/capabilities/setup/plan.render.ts +171 -76
  212. package/src/capabilities/setup/plan.ts +17 -12
  213. package/src/capabilities/setup/setup.mcp.correctness.test.ts +9 -9
  214. package/src/capabilities/setup/setup.render.test.ts +50 -36
  215. package/src/capabilities/setup/setup.render.ts +1 -1
  216. package/src/capabilities/setup/setup.test.ts +1126 -50
  217. package/src/capabilities/setup/setup.verb.ts +174 -68
  218. package/src/capabilities/setup/targets.ts +138 -60
  219. package/src/capabilities/setup/types.ts +10 -18
  220. package/src/capabilities/shared/index.ts +18 -6
  221. package/src/capabilities/shared/packageManager.test.ts +421 -6
  222. package/src/capabilities/shared/packageManager.ts +394 -59
  223. package/src/capabilities/skill/bundledSkills.test.ts +347 -0
  224. package/src/capabilities/skill/discover.ts +100 -11
  225. package/src/capabilities/skill/render.ts +2 -2
  226. package/src/capabilities/skill/skill.test.ts +17 -4
  227. package/src/capabilities/sources/installSkills.ts +181 -3
  228. package/src/capabilities/sources/runUpdate.ts +78 -2
  229. package/src/capabilities/sources/sources.test.ts +228 -0
  230. package/src/capabilities/sources/update.verb.ts +3 -3
  231. package/src/capabilities/surface.test.ts +14 -6
  232. package/src/capabilities/upgrade/runUpgrade.ts +45 -6
  233. package/src/capabilities/upgrade/types.ts +10 -2
  234. package/src/capabilities/upgrade/upgrade.render.ts +9 -2
  235. package/src/capabilities/upgrade/upgrade.test.ts +72 -0
  236. package/src/constants.ts +14 -1
  237. package/src/identity.test.ts +18 -5
  238. package/src/kernel/completion/entitySource.test.ts +15 -2
  239. package/src/kernel/completion/safety.test.ts +16 -9
  240. package/src/kernel/config/defaults.test.ts +19 -3
  241. package/src/kernel/config/readConfig.test.ts +1 -1
  242. package/src/kernel/index.ts +11 -2
  243. package/src/kernel/interactivity.test.ts +76 -0
  244. package/src/kernel/interactivity.ts +69 -0
  245. package/src/kernel/packs/compile.test.ts +92 -0
  246. package/src/kernel/packs/renderPack.ts +3 -3
  247. package/src/kernel/packs/resolveEntity.test.ts +75 -14
  248. package/src/kernel/packs/resolveEntity.ts +88 -24
  249. package/src/kernel/packs/runBodies.ts +84 -19
  250. package/src/kernel/packs/schema.ts +60 -4
  251. package/src/kernel/packs/sparql/applyFilters.ts +125 -4
  252. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
  253. package/src/kernel/packs/sparql/buildLookupQuery.ts +327 -45
  254. package/src/kernel/packs/toolDescription.test.ts +35 -1
  255. package/src/kernel/packs/types.ts +125 -2
  256. package/src/kernel/project/cli/buildProgram.test.ts +73 -2
  257. package/src/kernel/project/cli/buildProgram.ts +8 -1
  258. package/src/kernel/project/cli/dispatch.test.ts +5 -4
  259. package/src/kernel/project/cli/dispatch.ts +48 -25
  260. package/src/kernel/project/cli/globalFlags.ts +4 -1
  261. package/src/kernel/project/cli/helpFormat.ts +1 -2
  262. package/src/kernel/project/cli/rootHelp.ts +44 -8
  263. package/src/kernel/project/mcp/registerVerb.ts +20 -1
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.ts +1 -1
  266. package/src/kernel/render/style.ts +32 -9
  267. package/src/kernel/render/vocabulary.ts +276 -0
  268. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  269. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  270. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  271. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  272. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  273. package/src/kernel/runtime/graphpack/read.ts +14 -12
  274. package/src/kernel/runtime/loadSession.ts +3 -1
  275. package/src/kernel/runtime/probe.ts +1 -1
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +9 -3
  279. package/src/kernel/runtime/types.ts +17 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/types.ts +35 -6
  282. package/src/testing/PARITY_GAPS.ts +9 -3
  283. package/src/testing/behavioral/docExamples.test.ts +4 -2
  284. package/src/testing/behavioral/errorMatrix.mcp.test.ts +110 -7
  285. package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
  286. package/src/testing/behavioral/journeys.packageStories.test.ts +6 -3
  287. package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
  288. package/src/testing/eval/cases/readNouns.ts +245 -9
  289. package/src/testing/fixtures/blockGraph.ts +28 -13
  290. package/src/testing/fixtures/graph/canonical.ts +95 -12
  291. package/src/testing/helpers/runCli.test.ts +44 -0
  292. package/src/testing/helpers/runCli.ts +27 -3
  293. package/dist/src/capabilities/shared/bands.js +0 -16
  294. package/dist/src/capabilities/shared/bands.js.map +0 -1
  295. package/src/capabilities/shared/bands.ts +0 -16
@@ -0,0 +1,1226 @@
1
+ ---
2
+ name: anatomy-author
3
+ description: Write component anatomy specifications using the Design System Anatomy DSL; also runs as a guided tutorial on a worked example
4
+ ---
5
+
6
+ # Anatomy Author
7
+
8
+ Write component anatomy specifications using the Design System Anatomy DSL. This skill combines semantic discovery (understanding existing components) with procedural authoring (creating well-formed YAML specifications).
9
+
10
+ ## When to Use
11
+
12
+ - Writing anatomy specifications for new or existing components
13
+ - Documenting component structure before implementation
14
+ - Discussing component composition with the team ("these nodes should be siblings, not parent-child")
15
+ - Auditing a codebase against its specifications
16
+ - Supporting code generation from specifications
17
+ - Serving as step 7 of the `specify-component` flow (and of `specify-pattern`)
18
+
19
+ ## Description
20
+
21
+ The Anatomy DSL represents design system components as YAML trees with:
22
+ - **Named nodes**: Components with URIs (`global.component.button`)
23
+ - **Anonymous nodes**: Structural elements with roles (`content wrapper`)
24
+ - **Edges**: Parent-child relationships with cardinality and slot names
25
+ - **Styles**: Platform-agnostic properties using CTI-inspired keys
26
+
27
+ This skill helps you author these specifications correctly and consistently.
28
+
29
+ ## Working mode: assistant to a design authority
30
+
31
+ Most of the time this skill runs in collaboration with a senior designer or engineer —
32
+ they are the design authority, you are the assistant whose job is to make them
33
+ successful. Default to that mode: lay out where the flow stands, do the legwork
34
+ (discovery queries, template drafting, validation) and return digestible findings, and
35
+ bring structural decisions to the person as a recommendation plus a question rather
36
+ than a fait accompli. An anatomy encodes opinions the graph cannot settle alone —
37
+ draw the person's knowledge out and use it.
38
+
39
+ Full autonomy is the exception, not the default: run the whole flow alone only when
40
+ explicitly asked to. Even then, list every judgment call made unilaterally in the
41
+ final report so a human can revisit them.
42
+
43
+ Decision gates — in collaboration, pause at each and resolve it WITH the person:
44
+
45
+ 1. Node boundaries: what is a named subcomponent vs. an anonymous structural role.
46
+ 2. Slot names — they become API surface.
47
+ 3. Cardinality choices, especially optional vs. required parts.
48
+ 4. What the anatomy fixes vs. what is deliberately left free.
49
+
50
+ The full specification ships beside this file as `ANATOMY_DSL_SPEC.md` in the
51
+ installed skill folder — `pragma skill lookup anatomy-author` renders only this
52
+ SKILL.md, so open the sibling file directly when you need the complete spec (the
53
+ appendix below carries its type system).
54
+
55
+ ## Opening move: ask, or offer the tutorial
56
+
57
+ Activation is an opening, not a starting gun. Unless the first message already names
58
+ the work, ask for the starting point — as a suggestion carrying an example, not as a
59
+ form to fill in:
60
+
61
+ > To get started, tell me the component whose anatomy we're writing — a name if it is
62
+ > already in the graph, or just a sketch of its parts, like "a card with an image, a
63
+ > title and a row of actions".
64
+
65
+ A sketch is enough to begin: discovery turns it into the real node set, and the sketch
66
+ is what you check that set against. Ask again only for what the next step genuinely
67
+ blocks on.
68
+
69
+ If the activation message already carries the starting point, do not re-ask — say what
70
+ you took it to be, and go.
71
+
72
+ Offer the tutorial in the same breath, because this skill doubles as one:
73
+
74
+ > Or if you'd rather see the flow first, I can run it as a tutorial: I'll take a
75
+ > plausible example — a card, say — and walk you through it from discovery to the
76
+ > emitted anatomy.
77
+
78
+ Tutorial mode is the step narration below, turned up: the same outcome-path-conclusion
79
+ frame, with the reasoning at each decision gate made fully explicit and a check that
80
+ the person is with you before the next step. Stop short of anything that lands — no
81
+ anatomy written into the graph, no file committed — unless they ask to keep what you
82
+ built.
83
+
84
+ ## Every step is narrated: outcome, path, conclusion
85
+
86
+ Every step is addressed to the person — once before it runs, once when it ends. This is
87
+ not tutorial manner. It holds in the ordinary flow too: the person is the design
88
+ authority, and they can only steer a step they saw coming. A decision gate met cold is
89
+ a decision they cannot really make.
90
+
91
+ **Open** the step with what it is FOR — the outcome it should leave behind — and how
92
+ you are about to get there. **Close** it with a sentence saying what it established and
93
+ what that means for the step after. One sentence each is enough; the failure mode is
94
+ silence, not length, and a step whose outcome looks obvious still gets its sentence
95
+ rather than a shrug.
96
+
97
+ Do:
98
+
99
+ > **Discovery, before any DSL.** The outcome is the component's real context — what it
100
+ > is composed of, and what already names its parts — so the anatomy describes the
101
+ > system instead of inventing beside it. The path is `pragma block lookup Card`, then
102
+ > the subcomponent query below.
103
+ >
104
+ > …
105
+ >
106
+ > So: Card already carries a named `Card-Header` subcomponent and no named body — the
107
+ > anatomy inherits the first and has to decide the second, which is the first
108
+ > structural gate.
109
+
110
+ Don't:
111
+
112
+ > Discovery
113
+ >
114
+ > Card exists. Has Card-Header. No body node.
115
+
116
+ Both carry the same facts. Only the first says what the step was trying to achieve and
117
+ what it settled — and only the first lets the person cut in with the thing they know
118
+ and the graph does not, which is the whole reason they are here.
119
+
120
+ ## Asked about the skill: methodology and outcomes first
121
+
122
+ A question about what this skill does — what it covers, what its steps are, how it
123
+ works — is answered in that order: the METHOD it applies and the OUTCOMES it leaves
124
+ the person holding come first, the step-by-step breakdown comes after.
125
+
126
+ Two or three sentences of method is enough: what the skill treats as its object, the
127
+ discipline that makes it work, and what exists at the end that did not exist before.
128
+ The enumeration then reads as steps in service of something, rather than as a list to
129
+ be got through.
130
+
131
+ What this prevents is a table of contents standing in for an answer. A reply opening
132
+ with the step names tells someone who already knows the skill nothing new, and someone
133
+ who does not, nothing at all.
134
+
135
+ So, asked what this skill is:
136
+
137
+ > It writes component anatomies in the Anatomy DSL, combining semantic discovery —
138
+ > understanding what the graph already names — with procedural authoring. The
139
+ > structural choices are the substance: node boundaries, cardinality, and slot names,
140
+ > which become API surface. You end holding a well-formed anatomy whose every
141
+ > deliberate decision is one someone can revisit.
142
+
143
+ Then, and only then, the breakdown.
144
+
145
+ ## Anatomy falls under standards too
146
+
147
+ Emitted anatomy is Turtle, and Turtle has standards — the `turtle` category, with
148
+ `ui-blocks` governing block modelling (take the actual set from
149
+ `pragma standard categories`, not from this list). Pull them before authoring and hold
150
+ the Do/Don't pairs open while writing the DSL and its emitted TTL; a deviation from a
151
+ pulled standard is RECORDED next to the anatomy, ideally also filed as an issue. The
152
+ standards apply independent of any package dependency and are open to contribution:
153
+ a missing or wrong modelling rule is something to propose a change for, not to
154
+ silently work around.
155
+
156
+ ## Discovery Flow
157
+
158
+ Before writing an anatomy, understand the component's context:
159
+
160
+ ```bash
161
+ pragma block list # every component, pattern, layout and subcomponent, with its type and tier
162
+ pragma block lookup Card # full spec: anatomy, modifiers, properties (MCP: block_lookup)
163
+ pragma graph inspect ds:global.component.card # every triple on the entity
164
+ ```
165
+
166
+ A bare name can match blocks in several tiers, and `block lookup` silently picks one —
167
+ it resolves `ds:name` globally and cannot be steered to a tier.
168
+ Confirm the tier the lookup picked from its own `- Tier:` line; the name query below lists every tier that carries the name.
169
+ The name query in Workflow §1 is the one that lists them, with each block's IRI. If the
170
+ lookup picked the wrong tier's block, read the block you want with
171
+ `pragma graph inspect <IRI from that row>`, so the lookup step and the inspect step
172
+ describe the same block. If the name has no row at all, the block may be a Group —
173
+ `block list` covers no groups; the group pre-step in Workflow §1 lists them.
174
+
175
+ ### Discovery Queries
176
+
177
+ Common prefixes (`ds:`, `cs:`) are applied automatically — no PREFIX preamble needed.
178
+
179
+ **Find components by tier:**
180
+ ```bash
181
+ pragma graph query "SELECT ?component ?name WHERE {
182
+ ?component a ds:Component ;
183
+ ds:name ?name ;
184
+ ds:tier ds:global .
185
+ }"
186
+ ```
187
+
188
+ **Find subcomponents of a component:**
189
+ ```bash
190
+ pragma graph query "SELECT ?sub ?name WHERE {
191
+ ?sub a ds:Subcomponent ;
192
+ ds:name ?name ;
193
+ ds:parentComponent <https://ds.canonical.com/global.component.card> .
194
+ }"
195
+ ```
196
+
197
+ Inside a SPARQL body, a local name with MORE THAN ONE dot (`ds:global.component.card`)
198
+ does not parse — use the full IRI there: join `https://ds.canonical.com/` with the
199
+ dotted local name (giving `<https://ds.canonical.com/global.component.card>`), or copy
200
+ one from `pragma graph query` output, which prints absolute IRIs. Prefixed names with
201
+ no dot (`ds:global`, `ds:Component`) or a single dot (`ds:tag.needsdefinition`) work
202
+ as-is.
203
+
204
+ ### The empty-anatomy worklist
205
+
206
+ The Coda sync emits `ds:anatomyDsl` as an empty string for blocks whose anatomy is not
207
+ yet written — this query IS the to-do list; pick from it, never from a copied table:
208
+
209
+ ```bash
210
+ pragma graph query "SELECT ?b ?name WHERE { ?b ds:anatomyDsl ?a ; ds:name ?name . FILTER(STR(?a) = '') }"
211
+ ```
212
+
213
+ The worklist rows point at blocks whose data lives under `data/` — never hand-edit
214
+ that tree: it is regenerated destructively from Coda by CI, and hand edits are
215
+ overwritten by the next sync. An authored DSL lands in the block's spec draft under
216
+ `specs/` (see `specs/README.md`) or is pasted into Coda by a human.
217
+
218
+ ## DSL Reference
219
+
220
+ ### YAML Format
221
+
222
+ All anatomy files must start with `---` (YAML document start marker):
223
+
224
+ ```yaml
225
+ ---
226
+ node:
227
+ uri: global.component.button
228
+ # ...
229
+ ```
230
+
231
+ ### Type System Overview
232
+
233
+ ```
234
+ AnatomySpec
235
+ -> node: NamedNode (root must be named)
236
+
237
+ NamedNode
238
+ -> uri: string ("tier.type.name")
239
+ -> styles?: Record<string, TokenPath | TokenPath[] | PrimitiveValue>
240
+ -> edges?: Edge[]
241
+
242
+ AnonymousNode
243
+ -> role: string ("content wrapper")
244
+ -> styles?: Record<string, TokenPath | TokenPath[] | PrimitiveValue>
245
+ -> edges?: Edge[]
246
+
247
+ Edge
248
+ -> node?: NamedNode | AnonymousNode
249
+ -> switch?: Switch (mutually exclusive with node)
250
+ -> relation: Relation
251
+
252
+ Switch
253
+ -> on: "props" | "internal" | "override"
254
+ -> cases: SwitchCase[]
255
+
256
+ SwitchCase
257
+ -> uri?: string (shorthand)
258
+ -> node?: Node (full form)
259
+ -> default?: boolean (marks the default case)
260
+
261
+ Relation
262
+ -> cardinality: string ("1", "0..1", "0..*", "1..*")
263
+ -> slotName?: string ("default", "header", "icon")
264
+ ```
265
+
266
+ ### URI Encoding Convention
267
+
268
+ URIs follow turtle conventions with dot-separated paths: `tier.type.name`
269
+
270
+ | Symbol | Meaning | Example |
271
+ |--------|---------|---------|
272
+ | `.` | Path hierarchy | `global.component.button` |
273
+ | `_` | Word boundary from PascalCase | `ChartLegend` → `chart_legend` |
274
+ | `-` | Dot in compound names | `Card.Header` → `card-header` |
275
+
276
+ **Examples:**
277
+
278
+ | Component | URI |
279
+ |-----------|-----|
280
+ | `Button` | `global.component.button` |
281
+ | `Accordion.Item` | `global.subcomponent.accordion-item` |
282
+ | `ChartLegend` | `global.subcomponent.chart_legend` |
283
+ | `Card.Header` | `global.subcomponent.card-header` |
284
+
285
+ **Tiers and Types:**
286
+
287
+ The tier segment comes from the live tier set — run `pragma tier list` for the tiers
288
+ that exist today; never assign a tier from a remembered list. The type segment is one
289
+ of the ontology's UIBlock classes:
290
+
291
+ | Type | Use Case |
292
+ |------|----------|
293
+ | `component` | Standalone components |
294
+ | `subcomponent` | Parts of components (user-instantiable) |
295
+ | `pattern` | UX solutions combining components |
296
+ | `layout` | Space-dividing containers |
297
+ | `group` | Repeating series of ONE sibling block (the plural of a Component) |
298
+
299
+ (`pragma block list` omits groups — read them from the graph:
300
+ `pragma graph query "SELECT ?b ?name WHERE { ?b a ds:Group ; ds:name ?name }"`.)
301
+
302
+ **Parent Reference in Subcomponents:**
303
+
304
+ Subcomponents must reference their parent in the name:
305
+
306
+ | Labelled Name | Turtle URI |
307
+ |---------------|------------|
308
+ | `Timeline.Event` | `global.subcomponent.timeline-event` |
309
+ | `Timeline.ExpansionIndicator` | `global.subcomponent.timeline-expansion_indicator` |
310
+
311
+ ### Named vs Anonymous
312
+
313
+ | Type | Identifier | Use Case |
314
+ |------|------------|----------|
315
+ | Named | `uri` | User-instantiable components (things users compose in their code) |
316
+ | Anonymous | `role` | Internal structural elements not directly instantiated by users |
317
+
318
+ **Rule of thumb:** Ask "Can/should a user write `<ComponentName>` in their code?" If yes, use `uri`. If no, use `role`.
319
+
320
+ ### DRY Principle
321
+
322
+ When a node has a URI (is not anonymous), it references its own DSL file. Do not inline the full tree—reference the URI only. A named child MAY carry `styles:` that override its own anatomy in this context — live, `apps_landscape.component.password_constraints` sizes down its `global.component.icon` child — but never a copy of its subtree.
323
+
324
+ ```yaml
325
+ # Correct: reference only
326
+ edges:
327
+ - node:
328
+ uri: global.component.button
329
+ relation:
330
+ cardinality: "1"
331
+
332
+ # Incorrect: copying the child's own definition
333
+ edges:
334
+ - node:
335
+ uri: global.component.button
336
+ styles:
337
+ # ... full button styles duplicated here
338
+ relation:
339
+ cardinality: "1"
340
+
341
+ # Permitted: contextual style overrides (the live password_constraints → icon pair)
342
+ edges:
343
+ - node:
344
+ uri: global.component.icon
345
+ styles:
346
+ size.width: size/icon/small
347
+ size.height: size/icon/small
348
+ relation:
349
+ cardinality: "1"
350
+ ```
351
+
352
+ ### Cardinality Notation
353
+
354
+ | Notation | Meaning | Example Use |
355
+ |----------|---------|-------------|
356
+ | `"1"` or `"1..1"` | Exactly one (required) | Card body |
357
+ | `"0..1"` | Zero or one (optional) | Card header, Card footer |
358
+ | `"0..*"` | Zero or more | List items |
359
+ | `"1..*"` | One or more | Accordion items (need at least one) |
360
+ | `"2..5"` | Between 2 and 5 | Bounded repeats — live: KeyboardKeys' `"2..*"` (see the Group template below) |
361
+
362
+ ### Switch Construct
363
+
364
+ The `switch` construct models positions in the anatomy tree that can be filled by one of several alternatives. It operates at the edge level, parallel to `node`, making polymorphism explicit.
365
+
366
+ #### Syntax
367
+
368
+ ```yaml
369
+ edges:
370
+ - switch:
371
+ on: <discriminator>
372
+ cases:
373
+ - uri: <component-uri>
374
+ default: true # optional, marks default case
375
+ - uri: <component-uri>
376
+ - uri: $custom # reserved URI for user-provided components
377
+ relation:
378
+ cardinality: <cardinality>
379
+ slotName: <slot>
380
+ ```
381
+
382
+ #### Discriminator Vocabulary
383
+
384
+ | Value | Meaning | Use Case |
385
+ |-------|---------|----------|
386
+ | `props` | Consumer chooses via component props | `<Field type="checkbox">` renders Checkbox vs Radio |
387
+ | `internal` | Component manages choice internally | AsyncButton shows Loading/Success/Error based on state (hypothetical — see the labelled example below) |
388
+ | `override` | Consumer can replace with custom component | Timeline accepts custom Event component via slot |
389
+
390
+ This enum is NORMATIVE. Live anatomies that predate it may carry qualified
391
+ discriminators (`props/<prop>`) or a `with:` map in exploratory drafts — do not
392
+ copy those into new anatomies.
393
+
394
+ #### Shorthand Expansion
395
+
396
+ The shorthand `- uri: X` expands to `- node: { uri: X }`. This allows cases to be expressed concisely when no additional metadata is needed:
397
+
398
+ ```yaml
399
+ # Shorthand (common case)
400
+ cases:
401
+ - uri: global.subcomponent.checkbox_input
402
+
403
+ # Expands to full form
404
+ cases:
405
+ - node:
406
+ uri: global.subcomponent.checkbox_input
407
+ ```
408
+
409
+ When a case requires additional properties (styles, nested edges), use the full node form:
410
+
411
+ ```yaml
412
+ cases:
413
+ - node:
414
+ uri: global.subcomponent.textarea_input
415
+ styles:
416
+ size.height: hug
417
+ size.min.height: size/input/multiline
418
+ ```
419
+
420
+ #### Reserved URI: `$custom`
421
+
422
+ The `$custom` URI indicates that the position accepts a user-provided component:
423
+
424
+ ```yaml
425
+ cases:
426
+ - uri: global.subcomponent.timeline-event
427
+ - uri: $custom # consumer can provide custom component
428
+ ```
429
+
430
+ #### Examples
431
+
432
+ **Field with prop-based switch:**
433
+
434
+ ```yaml
435
+ ---
436
+ node:
437
+ uri: global.pattern.field
438
+ edges:
439
+ - switch:
440
+ on: props
441
+ cases:
442
+ - uri: global.subcomponent.checkbox_input
443
+ - uri: global.subcomponent.radio_input
444
+ - uri: global.subcomponent.text_input
445
+ relation:
446
+ cardinality: "1"
447
+ slotName: input
448
+ ```
449
+
450
+ **Async button with internal state switch** (the `async_button` family is hypothetical —
451
+ an illustration of the `internal` discriminator, not a live block):
452
+
453
+ ```yaml
454
+ ---
455
+ node:
456
+ uri: global.component.async_button
457
+ edges:
458
+ - switch:
459
+ on: internal
460
+ cases:
461
+ - uri: global.subcomponent.async_button-idle
462
+ - uri: global.subcomponent.async_button-loading
463
+ - uri: global.subcomponent.async_button-success
464
+ - uri: global.subcomponent.async_button-error
465
+ relation:
466
+ cardinality: "1"
467
+ ```
468
+
469
+ **Timeline with override-capable slot:**
470
+
471
+ ```yaml
472
+ ---
473
+ node:
474
+ uri: global.pattern.timeline
475
+ edges:
476
+ - switch:
477
+ on: override
478
+ cases:
479
+ - uri: global.subcomponent.timeline-event
480
+ - uri: $custom
481
+ relation:
482
+ cardinality: "1..*"
483
+ ```
484
+
485
+ ### Slot Names
486
+
487
+ Common slot conventions:
488
+
489
+ | Slot | Purpose |
490
+ |------|---------|
491
+ | `default` | Main content area (React children, Vue default slot) |
492
+ | `header` | Header content |
493
+ | `footer` | Footer content |
494
+ | `icon` | Icon placement |
495
+ | `label` | Text label |
496
+ | `media` | Image/video content |
497
+ | `actions` | Action buttons |
498
+
499
+ **Convention:** `slotName: default` implies the main children slot (`children` in React, default slot in Vue).
500
+
501
+ ### Style Key Categories
502
+
503
+ Styles use CTI-inspired dot-notation: `category.type[.item]`
504
+
505
+ #### Layout (typically invariant)
506
+ ```yaml
507
+ layout.type: stack | flow | grid
508
+ layout.direction: horizontal | vertical
509
+ layout.align: start | center | end | stretch
510
+ layout.justify: start | center | end | space-between | space-around
511
+ layout.wrap: true | false
512
+ layout.display: block | flex | grid | none
513
+ layout.flex: 1 | auto
514
+ layout.overflow: hidden | scroll | visible
515
+ ```
516
+
517
+ #### Spacing (invariant if literal, themeable if token)
518
+ ```yaml
519
+ spacing.internal: spacing/medium # padding (themeable)
520
+ spacing.external: spacing/large # margin (themeable)
521
+ spacing.gap: spacing/small # gap between children (themeable)
522
+ spacing.margin.bottom: spacing/small # specific margin
523
+ ```
524
+
525
+ #### Appearance (typically themeable)
526
+ ```yaml
527
+ appearance.background: color/surface/card
528
+ appearance.border: border/style/default
529
+ appearance.border.top: border/style/divider
530
+ appearance.shadow: shadow/elevated/medium
531
+ appearance.radius: shape/rounded/full
532
+ ```
533
+
534
+ #### Size (invariant if structural, themeable if token)
535
+ ```yaml
536
+ size.width: fill | hug | 100% # invariant
537
+ size.width: size/card/width # themeable
538
+ size.max.width: size/container/max
539
+ size.max.height: size/media/max
540
+ size.min.height: 48px
541
+ ```
542
+
543
+ #### Typography (typically themeable)
544
+ ```yaml
545
+ typography.size: font/size/body
546
+ typography.weight: font/weight/bold
547
+ typography.color: color/text/primary
548
+ typography.align: center | left | right # can be invariant
549
+ typography.line.height: 1.5
550
+ ```
551
+
552
+ #### Interaction (typically invariant)
553
+ ```yaml
554
+ interaction.cursor: pointer | default
555
+ interaction.transition.property: background
556
+ interaction.transition.duration: transition/duration # token = themeable
557
+ interaction.transition.timing: ease
558
+ ```
559
+
560
+ #### Object (for media)
561
+ ```yaml
562
+ object.fit: cover | contain | fill
563
+ object.position: center | top
564
+ ```
565
+
566
+ ## Templates
567
+
568
+ ### Basic Component Template
569
+
570
+ ```yaml
571
+ node:
572
+ uri: {tier}.component.{name}
573
+ styles:
574
+ # Structural (invariant)
575
+ layout.type: stack
576
+ layout.direction: vertical
577
+
578
+ # Themeable
579
+ appearance.background: color/surface/{name}
580
+ spacing.internal: spacing/medium
581
+
582
+ edges:
583
+ - node:
584
+ uri: {tier}.subcomponent.{name}-content
585
+ relation:
586
+ cardinality: "1..1"
587
+ slotName: default
588
+ ```
589
+
590
+ ### Component with Optional Parts
591
+
592
+ ```yaml
593
+ node:
594
+ uri: global.component.card
595
+ styles:
596
+ layout.type: stack
597
+ layout.direction: vertical
598
+ appearance.background: color/surface/card
599
+ appearance.shadow: shadow/card
600
+ appearance.radius: shape/rounded/full
601
+
602
+ edges:
603
+ # Named children reference their URI only (DRY Principle above) — each
604
+ # carries its own styles in its own anatomy; the live Card does the same.
605
+ - node:
606
+ uri: global.subcomponent.card-header
607
+ relation:
608
+ cardinality: "0..1" # optional
609
+ slotName: header
610
+
611
+ - node:
612
+ uri: global.subcomponent.card-content
613
+ relation:
614
+ cardinality: "1..1" # required
615
+ slotName: default
616
+
617
+ - node:
618
+ uri: global.subcomponent.card-footer
619
+ relation:
620
+ cardinality: "0..1" # optional
621
+ slotName: footer
622
+ ```
623
+
624
+ ### Nested Components (Repeating)
625
+
626
+ ```yaml
627
+ node:
628
+ uri: global.component.accordion
629
+ styles:
630
+ layout.type: stack
631
+ layout.direction: vertical
632
+ appearance.border: border/style/accordion
633
+
634
+ edges:
635
+ # The repeating child is NAMED — reference its URI only (DRY Principle
636
+ # above); its subtree lives in its own anatomy. The live Accordion does
637
+ # the same.
638
+ - node:
639
+ uri: global.subcomponent.accordion-item
640
+ relation:
641
+ cardinality: "1..*" # one or more items
642
+ slotName: default
643
+ ```
644
+
645
+ The child's own anatomy — a separate spec on `global.subcomponent.accordion-item`
646
+ (transcribed from the live block) — is where the subtree lives; its parts are
647
+ anonymous roles:
648
+
649
+ ```yaml
650
+ node:
651
+ uri: global.subcomponent.accordion-item
652
+ styles:
653
+ layout.type: stack
654
+ layout.direction: vertical
655
+ edges:
656
+ - node:
657
+ role: header tab
658
+ styles:
659
+ layout.type: flow
660
+ layout.direction: horizontal
661
+ layout.align: center
662
+ interaction.cursor: pointer
663
+ edges:
664
+ - node:
665
+ role: control
666
+ styles:
667
+ size.width: size/icon/small
668
+ size.height: size/icon/small
669
+ relation:
670
+ cardinality: "1"
671
+ - node:
672
+ role: heading
673
+ relation:
674
+ cardinality: "1"
675
+ slotName: default
676
+ relation:
677
+ cardinality: "1"
678
+ slotName: header
679
+
680
+ - node:
681
+ role: content panel
682
+ styles:
683
+ layout.overflow: hidden
684
+ relation:
685
+ cardinality: "1"
686
+ slotName: default
687
+ ```
688
+
689
+ ### Group (repeating siblings)
690
+
691
+ A Group is the plural of ONE sibling block: a named root carrying only layout and
692
+ spacing styles, and a single URI-only edge whose cardinality bounds the repetition.
693
+ Transcribed from the live `global.group.keyboard_keys`:
694
+
695
+ ```yaml
696
+ node:
697
+ uri: global.group.keyboard_keys
698
+ styles:
699
+ layout.display: inline-flex
700
+ layout.align: center
701
+ spacing.gap: spacing/horizontal/xsmall
702
+ edges:
703
+ - node:
704
+ uri: global.component.keyboard_key
705
+ relation:
706
+ cardinality: "2..*" # a group needs at least two keys
707
+ slotName: default
708
+ ```
709
+
710
+ ### Anonymous Wrapper Node
711
+
712
+ Use when you need a structural element without design system identity:
713
+
714
+ ```yaml
715
+ node:
716
+ uri: global.pattern.modal
717
+ styles:
718
+ layout.type: stack
719
+ appearance.background: color/surface/modal
720
+
721
+ edges:
722
+ - node:
723
+ role: backdrop overlay # anonymous - no uri
724
+ styles:
725
+ layout.display: block
726
+ appearance.background: color/overlay/dark
727
+ interaction.cursor: pointer
728
+ relation:
729
+ cardinality: "1..1"
730
+
731
+ - node:
732
+ role: content container # anonymous wrapper
733
+ styles:
734
+ layout.type: stack
735
+ size.max.width: size/modal/max
736
+ spacing.internal: spacing/large
737
+ edges:
738
+ - node:
739
+ uri: global.subcomponent.modal-header
740
+ relation:
741
+ cardinality: "0..1"
742
+ slotName: header
743
+ - node:
744
+ uri: global.subcomponent.modal-content
745
+ relation:
746
+ cardinality: "1..1"
747
+ slotName: default
748
+ relation:
749
+ cardinality: "1..1"
750
+ ```
751
+
752
+ ### Simple Button Example
753
+
754
+ The live Button models its icon and label as ANONYMOUS roles — a user writes `<Button>`
755
+ with props and children, never a `Button.Icon` — so the children carry `role`, not `uri`
756
+ (the Named vs Anonymous rule above):
757
+
758
+ ```yaml
759
+ node:
760
+ uri: global.component.button
761
+ styles:
762
+ # Structural
763
+ layout.type: flow
764
+ layout.direction: horizontal
765
+ layout.align: center
766
+ layout.justify: center
767
+
768
+ # Themeable
769
+ spacing.internal: spacing/button
770
+ spacing.gap: spacing/small
771
+ appearance.background: color/action/primary
772
+ appearance.radius: shape/rounded/button
773
+ typography.weight: font/weight/medium
774
+
775
+ # Interaction
776
+ interaction.cursor: pointer
777
+ interaction.transition.property: background, transform
778
+ interaction.transition.duration: transition/fast
779
+
780
+ edges:
781
+ - node:
782
+ role: icon
783
+ styles:
784
+ size.width: size/icon/small
785
+ size.height: size/icon/small
786
+ relation:
787
+ cardinality: "0..1"
788
+ slotName: icon
789
+
790
+ - node:
791
+ role: label
792
+ styles:
793
+ typography.size: font/size/button
794
+ typography.color: color/text/on-action
795
+ relation:
796
+ cardinality: "0..1"
797
+ slotName: default
798
+ ```
799
+
800
+ ## Workflow
801
+
802
+ ### 1. Identify the Component
803
+
804
+ First, a pre-step: could the name be a Group? Run the group query before the
805
+ lookup — neither `pragma block lookup` nor `pragma block list` covers groups, and
806
+ a name can be BOTH a Group and another tier's block (`BlogCards` is the Group
807
+ `sites.group.blog_cards` AND the Component
808
+ `sites_webcomponentsprototype.component.blog_cards`; the lookup answers with the
809
+ component and never mentions the group):
810
+
811
+ ```bash
812
+ pragma graph query "SELECT ?b ?name WHERE { ?b a ds:Group ; ds:name ?name }"
813
+ ```
814
+
815
+ If the name is in the answer and the Group is the block you were asked to write,
816
+ it is an existing block: take the URI from that query row (no §3 derivation),
817
+ gather its context with `pragma graph inspect <IRI>`, and continue from §2.
818
+
819
+ Otherwise, determine if the component exists in the design system:
820
+
821
+ ```bash
822
+ pragma block lookup Card
823
+ ```
824
+
825
+ Two branches:
826
+
827
+ - **Not found** (`ENTITY_NOT_FOUND`) — this is a NEW block: derive its URI in §3
828
+ below, then continue from §2.
829
+ - **Found** — this is a draft for an existing block (the empty-anatomy worklist path):
830
+ take the URI from the block's `pragma block list` row and skip §3. The list carries a
831
+ row per block it covers, not per tier declaring the name, and the lookup above
832
+ resolved it globally — the name query
833
+ `pragma graph query "SELECT ?b WHERE { ?b ds:name ?n . FILTER(LCASE(?n) = LCASE('<Name>')) }"`
834
+ lists every tier's IRI, so you take the one you meant.
835
+ Gather the full context — anatomy, modifiers, properties come with the lookup (a
836
+ bare name can match several tiers: see the multi-tier note in the Discovery Flow
837
+ above); every raw triple with:
838
+
839
+ ```bash
840
+ pragma graph inspect ds:global.component.card
841
+ ```
842
+
843
+ ### 2. Gather Structural Requirements
844
+
845
+ Ask:
846
+ - What are the main parts? (header, body, footer, icon, label)
847
+ - Which parts are required vs optional?
848
+ - Can any parts repeat?
849
+ - Are there anonymous structural wrappers needed?
850
+
851
+ ### 3. Determine Tier and Type
852
+
853
+ The URI is `{tier}.{type}.{snake_name}` (see URI Encoding Convention above). The tier
854
+ segment comes from the live tier set — run `pragma tier list` and pick from what it
855
+ answers, never from a remembered list; the type segment is one of the ontology's
856
+ UIBlock classes:
857
+
858
+ | Question | Answer | Result |
859
+ |----------|--------|--------|
860
+ | Is it universal? | Yes | `global.component.{name}` |
861
+ | Is it app-specific? | Yes | `{apps_tier}.component.{name}` — the app's own tier from `pragma tier list` (e.g. `apps_lxd`) |
862
+ | Is it a subpart? | Yes | `{tier}.subcomponent.{parent}-{part}` |
863
+ | Is it a UX solution? | Yes | `{tier}.pattern.{name}` |
864
+ | Does it divide space? | Yes | `{tier}.layout.{name}` |
865
+ | Is it many instances of ONE sibling block laid out together? | Yes | `{tier}.group.{name}` |
866
+
867
+ ### 4. Define Edges
868
+
869
+ For each child:
870
+ 1. Is it named (in DS) or anonymous (structural wrapper)?
871
+ 2. What is its cardinality?
872
+ 3. Does it map to a slot?
873
+
874
+ ### 5. Apply Styles
875
+
876
+ For each node, consider:
877
+ - **Layout**: How is content arranged?
878
+ - **Spacing**: Internal padding, gaps between children
879
+ - **Appearance**: Background, borders, shadows, radius
880
+ - **Size**: Width/height constraints
881
+ - **Typography**: Text styling (for text-containing nodes)
882
+ - **Interaction**: Cursor, transitions (for interactive nodes)
883
+
884
+ **Rule of thumb**:
885
+ - Structural values (stack, center, fill) = invariant
886
+ - Token references (spacing/medium) = themeable
887
+
888
+ ### 6. Validate
889
+
890
+ Check your anatomy against:
891
+ - [ ] Root node is named (has `uri`)
892
+ - [ ] All cardinalities are valid notation
893
+ - [ ] Style keys follow CTI convention
894
+ - [ ] Token paths use `/` delimiter
895
+ - [ ] Anonymous nodes have `role`, not `uri`
896
+ - [ ] Multi-word names encode word boundaries with `_`; `-` appears only where the name carries a dot (per the URI Encoding Convention table)
897
+ - [ ] Every non-root `uri:` either resolves (`pragma graph inspect ds:<uri>`) or is `$custom`, a template placeholder, or a new block or child this same spec introduces (§3)
898
+ - [ ] Named (`uri:`) children carry no copy of their own subtree — contextual style overrides only
899
+ - [ ] Nested components make semantic sense
900
+
901
+ ## Response Format
902
+
903
+ When creating an anatomy, respond with:
904
+
905
+ ```markdown
906
+ ## Anatomy: {ComponentName}
907
+
908
+ **URI:** `{tier}.{type}.{snake_name}`
909
+ **Tier:** {tier} - {rationale}
910
+
911
+ ### Structure Overview
912
+ - {Part 1} (required/optional)
913
+ - {Part 2} (required/optional)
914
+ - ...
915
+
916
+ ### Specification
917
+
918
+ \`\`\`yaml
919
+ {yaml content}
920
+ \`\`\`
921
+
922
+ ### Notes
923
+ - {Design decision 1}
924
+ - {Design decision 2}
925
+
926
+ ### Next Steps
927
+ 1. Land the DSL in the block's spec draft under `specs/` (see `specs/README.md`) —
928
+ as step 7 of a specify flow that is the step-6 spec file; never under `data/`
929
+ 2. A human enters the content into Coda, the database of record
930
+ ```
931
+
932
+ ## Tips
933
+
934
+ 1. **Start simple**: Begin with just structure, add styles incrementally
935
+ 2. **Use discovery**: Query existing components for patterns and consistency
936
+ 3. **Name thoughtfully**: URIs are identifiers - choose clear, consistent names
937
+ (see the URI Encoding Convention table above)
938
+ 4. **Annotate decisions**: Use YAML comments for non-obvious choices
939
+ 5. **Validate cardinality**: Think through edge cases (empty states, maximums)
940
+ 6. **Separate concerns**: Structural styles vs themeable styles
941
+ 7. **Anonymous nodes are OK**: Don't force DS identity on pure wrappers
942
+
943
+ ## Limitations
944
+
945
+ - Does not handle conditional rendering (show/hide based on state)
946
+ - Does not represent portal content (elements rendered elsewhere in DOM)
947
+ - Style inheritance is not yet supported (e.g., "inherits from Card")
948
+ - No runtime behavior specification (only static structure)
949
+
950
+ ## References
951
+
952
+ - [Open UI W3C Working Group](https://open-ui.org/) - Inspiration for code-based anatomy
953
+ - [Components as Data](https://medium.com/@nathanacurtis/components-as-data-2be178777f21) - Nathan Curtis's exploration
954
+ - [W3C Design Token Format](https://design-tokens.github.io/community-group/format/) - Token path conventions
955
+
956
+ ---
957
+
958
+ ## Appendix: Anatomy DSL Specification
959
+
960
+ The complete type system for the Anatomy DSL. This specification defines the formal structure for representing component anatomies.
961
+
962
+ ### Abstract
963
+
964
+ This specification presents a DSL to represent design system anatomies, providing an accurate platform-agnostic markup primitive to precede implementation.
965
+
966
+ ### Intended Usage
967
+
968
+ 1. **Documentation**: Implementation primitive for markup and style bindings
969
+ 2. **Specification**: Using the tree-based DSL to identify entities to implement and/or reuse
970
+ 3. **Discussion**: Using the DSL to discuss across conversations related to DS (e.g., "I believe these nodes should be siblings, not parents of one another")
971
+ 4. **Inference**: Supporting automated inference to audit a codebase against its specifications or to support code generation
972
+
973
+ ### Requirements
974
+
975
+ **General DSL Requirements:**
976
+ - The DSL MUST optimize for readability
977
+ - The DSL MUST be easy to get started with and use a familiar language
978
+ - The scope of the DSL MUST be minimized to cover only the strict necessary - additional specification information being covered in the schema
979
+
980
+ **Modelling Requirements:**
981
+ - The children of a node MUST be modelled through a reified relation for annotation purposes
982
+ - The DSL MUST support anonymous nodes (nodes that are not named components). In this case, the DSL should support role annotations
983
+ - The DSL MUST support reified annotations of cardinality
984
+ - The DSL MUST support all classes of UI Blocks identified in the ontology (Layout, Pattern, Component, Subcomponent, Group)
985
+
986
+ ### Type System
987
+
988
+ The children one-to-many relation follows inspiration from the [Relay connection pattern](https://relay.dev/graphql/connections.htm).
989
+
990
+ ```typescript
991
+ /* Reusable types */
992
+
993
+ /**
994
+ * Styles use a CTI (Category-Type-Item) inspired flat key structure
995
+ * for platform-agnostic UI properties.
996
+ *
997
+ * KEY NAMING CONVENTION:
998
+ * Properties follow dot-notation: "category.type[.item]"
999
+ * - Category: Primary concern (layout, spacing, appearance, typography, size)
1000
+ * - Type: Specific aspect within that category
1001
+ * - Item: Optional further specification
1002
+ *
1003
+ * INVARIANT VS THEMEABLE:
1004
+ * - Invariant (structural): layout.type, layout.direction, size.width: "fill"
1005
+ * - Themeable (brand/theme): appearance.*, spacing.* (when using tokens)
1006
+ */
1007
+ type Styles = Record<string, TokenPath | TokenPath[] | PrimitiveValue>;
1008
+
1009
+ /**
1010
+ * TokenPath: Forward-slash delimited path to a design token.
1011
+ * Examples: "spacing/medium", "color/surface/primary", "font/size/heading/1"
1012
+ */
1013
+ type TokenPath = string;
1014
+
1015
+ /**
1016
+ * PrimitiveValue: Direct values not resolved from tokens.
1017
+ * Examples: "stack", "center", "fill", 1.5, true
1018
+ */
1019
+ type PrimitiveValue = string | number | boolean;
1020
+
1021
+ /* Main types */
1022
+
1023
+ interface Relation {
1024
+ /**
1025
+ * Cardinality: Number of allowed instances.
1026
+ * - "1" : Exactly one (required)
1027
+ * - "0..1" : Zero or one (optional)
1028
+ * - "0..*" : Zero or more
1029
+ * - "1..*" : One or more
1030
+ * - "2..5" : Between 2 and 5
1031
+ */
1032
+ cardinality: string;
1033
+
1034
+ /**
1035
+ * Maps content to a specific slot in the parent component.
1036
+ * Common values: "default", "header", "footer", "icon", "label"
1037
+ */
1038
+ slotName?: string;
1039
+ }
1040
+
1041
+ /**
1042
+ * Base interface for all nodes in the anatomy tree.
1043
+ */
1044
+ interface BaseNode {
1045
+ /**
1046
+ * Style properties using CTI-inspired keys.
1047
+ *
1048
+ * Categories:
1049
+ * - layout.* : type, direction, align, justify, wrap, display, flex, overflow
1050
+ * - spacing.* : internal, external, gap, margin.*, padding.*
1051
+ * - appearance.*: background, border, shadow, radius
1052
+ * - size.* : width, height, max.*, min.*
1053
+ * - typography.*: size, weight, color, align, line.height
1054
+ * - interaction.*: cursor, transition.*
1055
+ * - object.* : fit, position
1056
+ */
1057
+ styles?: Styles;
1058
+
1059
+ /** Child nodes and their relationships. */
1060
+ edges?: Edge[];
1061
+ }
1062
+
1063
+ /**
1064
+ * Named nodes represent identifiable components in the design system.
1065
+ */
1066
+ interface NamedNode extends BaseNode {
1067
+ /**
1068
+ * Unique identifier following "tier.type.name" pattern.
1069
+ * Examples: "global.component.button", "apps.layout.application_layout"
1070
+ */
1071
+ uri: string;
1072
+ }
1073
+
1074
+ /**
1075
+ * Anonymous nodes represent structural elements without DS identity.
1076
+ */
1077
+ interface AnonymousNode extends BaseNode {
1078
+ /**
1079
+ * Human-readable description of the node's purpose.
1080
+ * Examples: "content wrapper", "spacer element", "icon container"
1081
+ */
1082
+ role: string;
1083
+ }
1084
+
1085
+ type Node = NamedNode | AnonymousNode;
1086
+
1087
+ /**
1088
+ * A switch fills one position with ONE of several alternatives.
1089
+ * The discriminator says who chooses: "props" (consumer chooses via
1090
+ * component props), "internal" (the component chooses from its own
1091
+ * state), "override" (the consumer may replace the default with a
1092
+ * custom component).
1093
+ */
1094
+ interface Switch {
1095
+ on: "props" | "internal" | "override";
1096
+ cases: SwitchCase[];
1097
+ }
1098
+
1099
+ /**
1100
+ * One switch alternative — exactly one of `uri` or `node`.
1101
+ * `uri` is the shorthand for `node: { uri }`; use the full `node` form
1102
+ * when the case carries styles or edges. The reserved URI `$custom`
1103
+ * marks a case filled by a user-provided component.
1104
+ */
1105
+ interface SwitchCase {
1106
+ uri?: string;
1107
+ node?: Node;
1108
+
1109
+ /** Marks the default case. */
1110
+ default?: boolean;
1111
+ }
1112
+
1113
+ interface Edge {
1114
+ /** The child node — exactly one of `node` or `switch`. */
1115
+ node?: Node;
1116
+
1117
+ /** A polymorphic position (mutually exclusive with node). */
1118
+ switch?: Switch;
1119
+
1120
+ relation: Relation;
1121
+ }
1122
+
1123
+ /**
1124
+ * Root specification for a component anatomy.
1125
+ * The top-level node must always be Named.
1126
+ */
1127
+ interface AnatomySpec {
1128
+ node: NamedNode;
1129
+ }
1130
+ ```
1131
+
1132
+ ### Language Choice
1133
+
1134
+ YAML is chosen as the markup language for its:
1135
+ - Indentation-based structure matching tree hierarchies
1136
+ - Readability over JSON's verbosity
1137
+ - Wide adoption and tooling support
1138
+
1139
+ Alternatives considered and rejected:
1140
+ - JSON: Too verbose for deep nesting
1141
+ - TOML: Difficulty with deeply nested maps
1142
+ - SDLang/KDL: Promising but lacking wide adoption
1143
+
1144
+ ### Complete Example: Accordion
1145
+
1146
+ ```yaml
1147
+ node:
1148
+ uri: global.component.accordion
1149
+ styles:
1150
+ # Structural (invariant)
1151
+ layout.type: stack
1152
+ layout.direction: vertical
1153
+
1154
+ # Themeable
1155
+ appearance.border: border/style/accordion
1156
+
1157
+ edges:
1158
+ # The repeating child is NAMED — reference its URI only (DRY Principle);
1159
+ # its subtree lives in its own anatomy. The live Accordion does the same.
1160
+ - node:
1161
+ uri: global.subcomponent.accordion-item
1162
+ relation:
1163
+ cardinality: "1..*"
1164
+ slotName: default
1165
+ ```
1166
+
1167
+ The child's own anatomy — a separate spec on `global.subcomponent.accordion-item`
1168
+ (transcribed from the live block) — carries the subtree as anonymous roles:
1169
+
1170
+ ```yaml
1171
+ node:
1172
+ uri: global.subcomponent.accordion-item
1173
+ styles:
1174
+ layout.type: stack
1175
+ layout.direction: vertical
1176
+ edges:
1177
+ - node:
1178
+ role: header tab
1179
+ styles:
1180
+ layout.type: flow
1181
+ layout.direction: horizontal
1182
+ layout.align: center
1183
+ interaction.cursor: pointer
1184
+ edges:
1185
+ - node:
1186
+ role: control
1187
+ styles:
1188
+ size.width: size/icon/small
1189
+ size.height: size/icon/small
1190
+ relation:
1191
+ cardinality: "1"
1192
+ - node:
1193
+ role: heading
1194
+ relation:
1195
+ cardinality: "1"
1196
+ slotName: default
1197
+ relation:
1198
+ cardinality: "1"
1199
+ slotName: header
1200
+
1201
+ - node:
1202
+ role: content panel
1203
+ styles:
1204
+ layout.overflow: hidden
1205
+ relation:
1206
+ cardinality: "1"
1207
+ slotName: default
1208
+ ```
1209
+
1210
+ ### Future Work
1211
+
1212
+ The following features would require additional work:
1213
+ - Representation of related nodes displayed in portals
1214
+ - Representation of conditional display
1215
+ - Inheritance reference for styles (e.g., "NetworkCard inherits from Card")
1216
+
1217
+ ## Support
1218
+
1219
+ If this skill leads somewhere broken — a command that errors, guidance that
1220
+ contradicts what the live system answers, a gap the flow cannot cover — you are not
1221
+ stuck:
1222
+
1223
+ - Raise an issue in the pragma repo: https://github.com/canonical/pragma/issues —
1224
+ include the skill name, what was run, and expected vs. actual outcome.
1225
+ - Or contact the design-system team owners directly through your organization's
1226
+ professional messaging channels for assistance.