@adia-ai/adia-ui-forge 0.1.3 → 0.8.1

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 (259) hide show
  1. package/.claude-plugin/plugin.json +4 -7
  2. package/CHANGELOG.md +35 -34
  3. package/README.md +35 -40
  4. package/agents/a2ui-engineer.md +22 -53
  5. package/agents/component-author.md +29 -0
  6. package/agents/framework-verifier.md +26 -0
  7. package/agents/release-engineer.md +21 -81
  8. package/agents/routing-corpus.json +293 -64
  9. package/bin/demo-postwrite-pattern-gate +94 -0
  10. package/bin/forge-lint +17 -2
  11. package/bin/sidecar-prewrite-guard +104 -0
  12. package/commands/deploy.md +9 -0
  13. package/commands/dogfood.md +10 -0
  14. package/commands/gen-review.md +9 -0
  15. package/commands/release.md +10 -0
  16. package/hooks/hooks.json +15 -0
  17. package/package.json +3 -6
  18. package/references/contracts/a2ui-mcp-surface.md +35 -0
  19. package/references/contracts/migration-guide-format.md +34 -0
  20. package/references/shared/content-trust.md +19 -74
  21. package/skills/adia-a2ui/SKILL.md +99 -0
  22. package/skills/{adia-ui-a2ui → adia-a2ui}/evals/routing-corpus.json +10 -10
  23. package/skills/adia-a2ui/references/anti-patterns.md +27 -0
  24. package/skills/adia-a2ui/references/chunk-authoring.md +71 -0
  25. package/skills/adia-a2ui/references/corpus-discipline.md +68 -0
  26. package/skills/adia-a2ui/references/eval-diagnostics.md +86 -0
  27. package/skills/adia-a2ui/references/format-extension-decisions.md +66 -0
  28. package/skills/adia-a2ui/references/leverage-rules.md +52 -0
  29. package/skills/adia-a2ui/references/mcp-pipeline-ops.md +83 -0
  30. package/skills/adia-a2ui/references/mcp-tool-reference.md +59 -0
  31. package/skills/adia-a2ui/references/pipeline-overview.md +115 -0
  32. package/skills/adia-a2ui/references/semantic-fail-lifting.md +74 -0
  33. package/skills/adia-a2ui/references/strategy-engines.md +109 -0
  34. package/skills/adia-a2ui/references/zettel-calibration.md +104 -0
  35. package/skills/adia-author/SKILL.md +112 -0
  36. package/skills/adia-author/evals/routing-corpus.json +222 -0
  37. package/skills/{adia-ui-authoring → adia-author}/references/anti-patterns.md +33 -4
  38. package/skills/{adia-ui-authoring → adia-author}/references/authoring-cycle.md +9 -11
  39. package/skills/adia-author/references/canonical-pattern-index.md +243 -0
  40. package/skills/{adia-ui-authoring → adia-author}/references/code-style.md +19 -21
  41. package/skills/adia-author/references/common-gotchas.md +129 -0
  42. package/skills/adia-author/references/composite-demo-protocol.md +271 -0
  43. package/skills/{adia-ui-authoring → adia-author}/references/css-patterns.md +19 -6
  44. package/skills/{adia-ui-authoring → adia-author}/references/lifecycle-patterns.md +1 -0
  45. package/skills/{adia-ui-authoring → adia-author}/references/llm-bridge.md +3 -5
  46. package/skills/{adia-ui-authoring → adia-author}/references/module-promotion.md +20 -38
  47. package/skills/{adia-ui-authoring → adia-author}/references/primitive-audit.md +2 -4
  48. package/skills/{adia-ui-authoring → adia-author}/references/shell-patterns.md +12 -18
  49. package/skills/{adia-ui-authoring → adia-author}/references/token-contract.md +5 -7
  50. package/skills/{adia-ui-authoring → adia-author}/references/worked-example.md +3 -3
  51. package/skills/{adia-ui-authoring → adia-author}/references/yaml-contract.md +3 -3
  52. package/skills/{adia-ui-authoring → adia-author}/scripts/build-canonical-pattern-index.mjs +3 -3
  53. package/skills/adia-deploy/SKILL.md +128 -0
  54. package/skills/{adia-ui-ops → adia-deploy}/references/deploy-playbooks.md +47 -30
  55. package/skills/adia-dogfood/SKILL.md +124 -0
  56. package/skills/adia-dogfood/references/admin-shell-anatomy.md +77 -0
  57. package/skills/adia-dogfood/references/app-shell-pitfalls.md +66 -0
  58. package/skills/adia-dogfood/references/card-anatomy-sweep.md +66 -0
  59. package/skills/adia-dogfood/references/html-attr-sweep.md +63 -0
  60. package/skills/adia-dogfood/references/native-leak-annotations.md +73 -0
  61. package/skills/adia-dogfood/references/visual-probe-triage.md +80 -0
  62. package/skills/{adia-ui-dogfood → adia-dogfood}/scripts/analyze.mjs +30 -14
  63. package/skills/adia-gen-review/SKILL.md +127 -0
  64. package/skills/adia-gen-review/references/corpus-html-patterns.md +279 -0
  65. package/skills/adia-gen-review/references/loop-protocol.md +216 -0
  66. package/skills/adia-gen-review/references/rubric-cosmetic.md +100 -0
  67. package/skills/adia-gen-review/references/rubric-decompose.md +101 -0
  68. package/skills/adia-gen-review/references/rubric-score.md +214 -0
  69. package/skills/{adia-ui-gen-review → adia-gen-review}/references/scores.schema.json +2 -2
  70. package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-coverage-audit.mjs +4 -4
  71. package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-decompose.mjs +10 -6
  72. package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/gen-review-status.mjs +5 -5
  73. package/skills/{adia-ui-gen-review → adia-gen-review}/scripts/validate-cycle-scores.mjs +5 -5
  74. package/skills/adia-llm-internals/SKILL.md +77 -0
  75. package/skills/{adia-ui-llm → adia-llm-internals}/references/adapter-contract.md +4 -2
  76. package/skills/{adia-ui-llm → adia-llm-internals}/references/add-a-provider.md +4 -5
  77. package/skills/{adia-ui-llm → adia-llm-internals}/references/bridge-facade.md +10 -3
  78. package/skills/{adia-ui-llm → adia-llm-internals}/references/browser-proxy-boundary.md +1 -1
  79. package/skills/{adia-ui-llm → adia-llm-internals}/references/model-registry.md +5 -3
  80. package/skills/{adia-ui-llm → adia-llm-internals}/references/streaming-sse.md +2 -2
  81. package/skills/adia-release/SKILL.md +72 -0
  82. package/skills/adia-release/references/changelog-discipline.md +119 -0
  83. package/skills/adia-release/references/cut-procedure.md +247 -0
  84. package/skills/adia-release/references/gates-catalog.md +222 -0
  85. package/skills/adia-release/references/independent-package-release.md +52 -0
  86. package/skills/adia-release/references/migration-guide-authoring.md +86 -0
  87. package/skills/adia-release/references/notes-authoring.md +90 -0
  88. package/skills/adia-release/references/recovery-paths.md +106 -0
  89. package/skills/{adia-ui-release → adia-release}/scripts/bump.mjs +28 -3
  90. package/skills/{adia-ui-release → adia-release}/scripts/dispatch-publish.mjs +10 -7
  91. package/skills/{adia-ui-release → adia-release}/scripts/insert-stub.mjs +1 -1
  92. package/skills/{adia-ui-release → adia-release}/scripts/package-paths.mjs +6 -0
  93. package/skills/{adia-ui-release → adia-release}/scripts/promote-unreleased.mjs +1 -1
  94. package/skills/{adia-ui-release → adia-release}/scripts/release-pack.mjs +38 -31
  95. package/skills/{adia-ui-release → adia-release}/scripts/tag-lockstep.mjs +7 -3
  96. package/skills/adia-site-docs/SKILL.md +68 -0
  97. package/skills/adia-site-docs/evals/audit-report.md +30 -0
  98. package/skills/adia-site-docs/evals/routing-corpus.json +176 -0
  99. package/skills/adia-site-docs/intent.md +72 -0
  100. package/agents/README.md +0 -209
  101. package/agents/author.md +0 -56
  102. package/agents/repo-steward.md +0 -56
  103. package/agents/spec-architect.md +0 -53
  104. package/agents/tech-lead.md +0 -57
  105. package/agents/verifier.md +0 -55
  106. package/bin/lib/audit-axes.mjs +0 -555
  107. package/bin/lib/dry-run-irreversible.mjs +0 -236
  108. package/bin/lib/run-skill-evals.mjs +0 -487
  109. package/bin/lib/teach-router.mjs +0 -250
  110. package/commands/adia-forge-a2ui.md +0 -10
  111. package/commands/adia-forge-author.md +0 -10
  112. package/commands/adia-forge-dogfood.md +0 -8
  113. package/commands/adia-forge-llm.md +0 -8
  114. package/commands/adia-forge-orient.md +0 -10
  115. package/commands/adia-forge-release.md +0 -8
  116. package/commands/adia-forge-review.md +0 -10
  117. package/references/shared/pev-rationale.md +0 -64
  118. package/references/shared/skill-conventions.md +0 -133
  119. package/skills/adia-ui-a2ui/CHANGELOG.md +0 -32
  120. package/skills/adia-ui-a2ui/SKILL.md +0 -243
  121. package/skills/adia-ui-a2ui/evals/adversarial-corpus.json +0 -75
  122. package/skills/adia-ui-a2ui/evals/teach-routing-cases.json +0 -100
  123. package/skills/adia-ui-a2ui/references/anti-patterns.md +0 -24
  124. package/skills/adia-ui-a2ui/references/chunk-authoring.md +0 -88
  125. package/skills/adia-ui-a2ui/references/corpus-discipline.md +0 -56
  126. package/skills/adia-ui-a2ui/references/eval-diagnostics.md +0 -127
  127. package/skills/adia-ui-a2ui/references/fragment-graph.md +0 -91
  128. package/skills/adia-ui-a2ui/references/mcp-pipeline-ops.md +0 -106
  129. package/skills/adia-ui-a2ui/references/mcp-tool-reference.md +0 -398
  130. package/skills/adia-ui-a2ui/references/pipeline-overview.md +0 -175
  131. package/skills/adia-ui-a2ui/references/semantic-fail-lifting.md +0 -120
  132. package/skills/adia-ui-a2ui/references/strategy-engines.md +0 -111
  133. package/skills/adia-ui-a2ui/references/teach-protocol.md +0 -220
  134. package/skills/adia-ui-a2ui/references/zettel-calibration.md +0 -93
  135. package/skills/adia-ui-a2ui/scripts/audit-a2ui-roster.mjs +0 -96
  136. package/skills/adia-ui-a2ui/scripts/teach-route.mjs +0 -157
  137. package/skills/adia-ui-a2ui/skill.json +0 -38
  138. package/skills/adia-ui-authoring/CHANGELOG.md +0 -32
  139. package/skills/adia-ui-authoring/SKILL.md +0 -256
  140. package/skills/adia-ui-authoring/assets/case-studies/admin-shell-decomposition.md +0 -101
  141. package/skills/adia-ui-authoring/assets/case-studies/maxtokens-32768-discovery.md +0 -109
  142. package/skills/adia-ui-authoring/assets/case-studies/theme-panel-promotion.md +0 -113
  143. package/skills/adia-ui-authoring/evals/adversarial-design-plan-gates.json +0 -138
  144. package/skills/adia-ui-authoring/evals/routing-corpus.json +0 -245
  145. package/skills/adia-ui-authoring/references/canonical-pattern-index.md +0 -179
  146. package/skills/adia-ui-authoring/references/common-gotchas.md +0 -93
  147. package/skills/adia-ui-authoring/references/composite-demo-protocol.md +0 -1084
  148. package/skills/adia-ui-authoring/references/teach-protocol.md +0 -428
  149. package/skills/adia-ui-authoring/scripts/audit-authoring-roster.mjs +0 -148
  150. package/skills/adia-ui-authoring/skill.json +0 -45
  151. package/skills/adia-ui-dogfood/CHANGELOG.md +0 -17
  152. package/skills/adia-ui-dogfood/README.md +0 -62
  153. package/skills/adia-ui-dogfood/SKILL.md +0 -866
  154. package/skills/adia-ui-dogfood/skill.json +0 -40
  155. package/skills/adia-ui-forge/SKILL.md +0 -88
  156. package/skills/adia-ui-forge/evals/routing-corpus.json +0 -30
  157. package/skills/adia-ui-gen-review/CHANGELOG.md +0 -108
  158. package/skills/adia-ui-gen-review/SKILL.md +0 -266
  159. package/skills/adia-ui-gen-review/references/loop-protocol.md +0 -712
  160. package/skills/adia-ui-gen-review/references/rubric-cosmetic.md +0 -144
  161. package/skills/adia-ui-gen-review/references/rubric-decompose.md +0 -117
  162. package/skills/adia-ui-gen-review/references/rubric-score.md +0 -249
  163. package/skills/adia-ui-gen-review/references/teach-protocol.md +0 -214
  164. package/skills/adia-ui-gen-review/skill.json +0 -23
  165. package/skills/adia-ui-llm/CHANGELOG.md +0 -25
  166. package/skills/adia-ui-llm/SKILL.md +0 -165
  167. package/skills/adia-ui-llm/evals/adversarial-corpus.json +0 -75
  168. package/skills/adia-ui-llm/evals/routing-corpus.json +0 -30
  169. package/skills/adia-ui-llm/evals/teach-routing-cases.json +0 -73
  170. package/skills/adia-ui-llm/references/teach-protocol.md +0 -78
  171. package/skills/adia-ui-llm/scripts/audit-llm-roster.mjs +0 -93
  172. package/skills/adia-ui-llm/scripts/teach-route.mjs +0 -119
  173. package/skills/adia-ui-llm/skill.json +0 -33
  174. package/skills/adia-ui-ops/CHANGELOG.md +0 -291
  175. package/skills/adia-ui-ops/SKILL.md +0 -401
  176. package/skills/adia-ui-ops/references/INDEX.md +0 -158
  177. package/skills/adia-ui-ops/references/audit-cadence.md +0 -263
  178. package/skills/adia-ui-ops/references/audit-patterns/archive-link-sweep.md +0 -96
  179. package/skills/adia-ui-ops/references/audit-patterns/audit-history-ledger.md +0 -162
  180. package/skills/adia-ui-ops/references/audit-patterns/browser-bundle-node-imports.md +0 -154
  181. package/skills/adia-ui-ops/references/audit-patterns/changelog-unreleased-bloat.md +0 -75
  182. package/skills/adia-ui-ops/references/audit-patterns/coverage-gaps.md +0 -187
  183. package/skills/adia-ui-ops/references/audit-patterns/entry-file-coverage.md +0 -122
  184. package/skills/adia-ui-ops/references/audit-patterns/format-hygiene.md +0 -217
  185. package/skills/adia-ui-ops/references/audit-patterns/lockstep-versioning.md +0 -113
  186. package/skills/adia-ui-ops/references/audit-patterns/memory-fragmentation.md +0 -180
  187. package/skills/adia-ui-ops/references/audit-patterns/orphan-detection.md +0 -202
  188. package/skills/adia-ui-ops/references/audit-patterns/pointer-validation.md +0 -180
  189. package/skills/adia-ui-ops/references/audit-patterns/redundancy-detection.md +0 -210
  190. package/skills/adia-ui-ops/references/audit-patterns/spec-dating-sweep.md +0 -91
  191. package/skills/adia-ui-ops/references/audit-patterns/stale-content.md +0 -182
  192. package/skills/adia-ui-ops/references/audit-patterns/staleness-tooling.md +0 -156
  193. package/skills/adia-ui-ops/references/audit-patterns/token-waste-detection.md +0 -196
  194. package/skills/adia-ui-ops/references/doc-types/adr-pattern.md +0 -210
  195. package/skills/adia-ui-ops/references/doc-types/architecture-md.md +0 -190
  196. package/skills/adia-ui-ops/references/doc-types/changelog.md +0 -183
  197. package/skills/adia-ui-ops/references/doc-types/decisions-log.md +0 -146
  198. package/skills/adia-ui-ops/references/doc-types/plan-roadmap.md +0 -182
  199. package/skills/adia-ui-ops/references/doc-types/postmortem-pattern.md +0 -206
  200. package/skills/adia-ui-ops/references/genres/prose-and-writing.md +0 -149
  201. package/skills/adia-ui-ops/references/guidance/context-budget.md +0 -137
  202. package/skills/adia-ui-ops/references/guidance/llm-doc-writing.md +0 -116
  203. package/skills/adia-ui-ops/references/guidance/reliability-dial.md +0 -186
  204. package/skills/adia-ui-ops/references/recipes/adr-introduction.md +0 -211
  205. package/skills/adia-ui-ops/references/recipes/audit-existing-repo.md +0 -234
  206. package/skills/adia-ui-ops/references/recipes/cold-start-harvest.md +0 -263
  207. package/skills/adia-ui-ops/references/recipes/concurrent-learnings-merge.md +0 -158
  208. package/skills/adia-ui-ops/references/recipes/continuous-learning-loop.md +0 -169
  209. package/skills/adia-ui-ops/references/recipes/external-reference-verification.md +0 -158
  210. package/skills/adia-ui-ops/references/recipes/findings-index-readout.md +0 -126
  211. package/skills/adia-ui-ops/references/recipes/greenfield-setup.md +0 -252
  212. package/skills/adia-ui-ops/references/recipes/harvest-repo-brain.md +0 -169
  213. package/skills/adia-ui-ops/references/recipes/import-repo-brain-harvest.md +0 -153
  214. package/skills/adia-ui-ops/references/recipes/memory-organization.md +0 -182
  215. package/skills/adia-ui-ops/references/recipes/recommend-then-validate.md +0 -199
  216. package/skills/adia-ui-ops/references/recipes/self-healing-hooks.md +0 -366
  217. package/skills/adia-ui-ops/references/recipes/skill-stewardship-loop.md +0 -113
  218. package/skills/adia-ui-ops/references/standards/agents-md-spec.md +0 -138
  219. package/skills/adia-ui-ops/references/standards/claude-md-convention.md +0 -123
  220. package/skills/adia-ui-ops/references/standards/cross-tool-matrix.md +0 -85
  221. package/skills/adia-ui-ops/references/standards/readme-conventions.md +0 -232
  222. package/skills/adia-ui-ops/references/teach-protocol.md +0 -215
  223. package/skills/adia-ui-ops/scripts/audit-ops-roster.mjs +0 -104
  224. package/skills/adia-ui-ops/skill.json +0 -65
  225. package/skills/adia-ui-release/CHANGELOG.md +0 -66
  226. package/skills/adia-ui-release/SKILL.md +0 -323
  227. package/skills/adia-ui-release/assets/case-studies/2026-05-20-batch-push-v0.6.14-v0.6.15.md +0 -144
  228. package/skills/adia-ui-release/assets/case-studies/2026-05-20-corpus-drift-remediation-v0.6.15.md +0 -155
  229. package/skills/adia-ui-release/assets/case-studies/2026-05-20-version-skip-correction-v0.6.12.md +0 -114
  230. package/skills/adia-ui-release/assets/case-studies/2026-05-21-author-from-scratch-v0.6.18.md +0 -139
  231. package/skills/adia-ui-release/assets/case-studies/2026-05-21-feedback37-retraction-v0.6.21.md +0 -124
  232. package/skills/adia-ui-release/assets/case-studies/2026-05-21-fn1-enrichment-pass-v0.6.19.md +0 -125
  233. package/skills/adia-ui-release/assets/case-studies/2026-05-21-stale-test-detection-v0.6.20.md +0 -142
  234. package/skills/adia-ui-release/assets/case-studies/2026-05-23-freshness-gate-recovery-v0.6.32.md +0 -97
  235. package/skills/adia-ui-release/assets/case-studies/2026-05-26-catalog-drift-recurring-v0.6.40.md +0 -147
  236. package/skills/adia-ui-release/assets/templates/stub-changelog.template.md +0 -22
  237. package/skills/adia-ui-release/evals/evals.json +0 -164
  238. package/skills/adia-ui-release/references/changelog-discipline.md +0 -250
  239. package/skills/adia-ui-release/references/cycle-happy-path.md +0 -520
  240. package/skills/adia-ui-release/references/exe-deploy.md +0 -149
  241. package/skills/adia-ui-release/references/gates-catalog.md +0 -778
  242. package/skills/adia-ui-release/references/independent-package-release.md +0 -129
  243. package/skills/adia-ui-release/references/ledger-discipline.md +0 -232
  244. package/skills/adia-ui-release/references/migration-guide-authoring.md +0 -174
  245. package/skills/adia-ui-release/references/multi-agent-baseline.md +0 -207
  246. package/skills/adia-ui-release/references/notes-authoring.md +0 -212
  247. package/skills/adia-ui-release/references/recovery-paths.md +0 -262
  248. package/skills/adia-ui-release/references/rollup-notes.md +0 -208
  249. package/skills/adia-ui-release/references/teach-protocol.md +0 -468
  250. package/skills/adia-ui-release/scripts/audit-gate-roster.mjs +0 -196
  251. package/skills/adia-ui-release/scripts/make-ledger.mjs +0 -200
  252. package/skills/adia-ui-release/skill.json +0 -77
  253. package/skills/dogfood-sweep/CHANGELOG.md +0 -37
  254. package/skills/dogfood-sweep/README.md +0 -105
  255. package/skills/dogfood-sweep/SKILL.md +0 -1000
  256. package/skills/dogfood-sweep/analyze.mjs +0 -600
  257. package/skills/dogfood-sweep/skill.json +0 -31
  258. /package/skills/{adia-ui-authoring → adia-author}/references/api-contract.md +0 -0
  259. /package/skills/{adia-ui-release → adia-release}/scripts/assert-monorepo-root.mjs +0 -0
@@ -1,555 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * audit-axes.mjs — Universal audit axis library
4
- *
5
- * Composable audit functions for rollup-family skill audit scripts.
6
- * Per-skill `scripts/audit-<name>-roster.mjs` imports and composes these
7
- * with their own skill-specific axes.
8
- *
9
- * Usage:
10
- * import * as axes from '${CLAUDE_PLUGIN_ROOT}/bin/lib/audit-axes.mjs';
11
- * const result = axes.manifestEnforcement({ skillDir, skillMd, skillJson });
12
- *
13
- * Each function returns:
14
- * { axis, axis_num, status: 'ok'|'drift', findings: [...], summary }
15
- *
16
- * All path arguments should be absolute.
17
- *
18
- * @module audit-axes
19
- */
20
-
21
- import fs from 'node:fs';
22
- import path from 'node:path';
23
-
24
- // ─── helpers ──────────────────────────────────────────────────────────────────
25
-
26
- /** Recursively walk a directory, returning relative paths of matching files. */
27
- function walkDir(dir, { include = null, exclude = ['node_modules', '.git', 'docs'] } = {}) {
28
- const results = [];
29
- if (!fs.existsSync(dir)) return results;
30
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
31
- if (exclude.some(x => entry.name === x)) continue;
32
- const fullPath = path.join(dir, entry.name);
33
- if (entry.isDirectory()) {
34
- results.push(...walkDir(fullPath, { include, exclude }));
35
- } else {
36
- if (!include || include.some(ext => entry.name.endsWith(ext))) {
37
- results.push(fullPath);
38
- }
39
- }
40
- }
41
- return results;
42
- }
43
-
44
- /** Read file; return '' if missing. */
45
- function readSafe(p) {
46
- try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
47
- }
48
-
49
- /** Parse YAML frontmatter from a markdown file. Returns {} if none found. */
50
- function parseFrontmatter(content) {
51
- const match = content.match(/^---\n([\s\S]*?)\n---/);
52
- if (!match) return {};
53
- const result = {};
54
- for (const line of match[1].split('\n')) {
55
- const m = line.match(/^(\w+):\s*['"]?(.*?)['"]?\s*$/);
56
- if (m) result[m[1]] = m[2];
57
- }
58
- return result;
59
- }
60
-
61
- /** Extract all markdown links [text](path) from content, with line numbers. */
62
- function extractMarkdownLinks(content, filePath) {
63
- const links = [];
64
- const lines = content.split('\n');
65
- let inFence = false;
66
- for (let i = 0; i < lines.length; i++) {
67
- const line = lines[i];
68
- if (/^```/.test(line)) { inFence = !inFence; continue; }
69
- if (inFence) continue;
70
- // Strip inline code spans before checking for links (avoids false positives
71
- // like `[link](references/X.md)` used as format examples in documentation)
72
- const lineStripped = line.replace(/`[^`]*`/g, '``');
73
- // Match [text](url) but not [text](#anchor), http links, or mailto
74
- const re = /\[([^\]]*)\]\(([^)]+)\)/g;
75
- let m;
76
- while ((m = re.exec(lineStripped)) !== null) {
77
- const url = m[2];
78
- if (url.startsWith('http') || url.startsWith('#') || url.startsWith('mailto:')) continue;
79
- // Skip home-directory paths (shell paths, not relative file paths)
80
- if (url.startsWith('~/')) continue;
81
- // Skip template placeholders like <repo>, <name>, <path>
82
- if (/<[^>]+>/.test(url)) continue;
83
- // Skip bare single-word paths that look like example placeholders
84
- if (/^[a-z]+$/.test(url) && !url.includes('/')) continue;
85
- // Strip fragment
86
- const cleanUrl = url.split('#')[0];
87
- if (!cleanUrl) continue;
88
- links.push({ text: m[1], url: cleanUrl, line: i + 1, file: filePath });
89
- }
90
- }
91
- return links;
92
- }
93
-
94
- /** Make a finding object. */
95
- function finding(type, message, extra = {}) {
96
- return { type, message, ...extra };
97
- }
98
-
99
- // ─── axes ──────────────────────────────────────────────────────────────────────
100
-
101
- /**
102
- * Axis 1 (Manifest enforcement) — recursive walker.
103
- * Checks that skill.json files[] matches all content files on disk.
104
- * Reports both directions: on-disk-but-undeclared + declared-but-missing.
105
- *
106
- * Scans: references/, scripts/, evals/, assets/ (recursive) + top-level .md + .json (excl. skill.json itself)
107
- * Excludes: docs/ (refactor-specs), node_modules, .git
108
- *
109
- * @param {{ skillDir: string, skillJson: string }} ctx
110
- */
111
- export function manifestEnforcement(ctx) {
112
- const { skillDir, skillJson } = ctx;
113
- const pkg = JSON.parse(readSafe(skillJson) || '{}');
114
- const declared = new Set((pkg.files || []).map(f => path.resolve(skillDir, f)));
115
-
116
- // Walk the skill directory for all content files
117
- const onDisk = new Set();
118
-
119
- // Top-level: *.md, *.json (except node_modules, docs/)
120
- for (const entry of fs.readdirSync(skillDir, { withFileTypes: true })) {
121
- if (!entry.isFile()) continue;
122
- if (!/\.(md|json|mjs|js)$/.test(entry.name)) continue;
123
- onDisk.add(path.join(skillDir, entry.name));
124
- }
125
-
126
- // Subdirectories: references/, scripts/, evals/, assets/ — recursive, excluding docs/
127
- for (const subDir of ['references', 'scripts', 'evals', 'assets']) {
128
- const full = path.join(skillDir, subDir);
129
- for (const f of walkDir(full, { include: ['.md', '.json', '.mjs', '.js', '.ts'] })) {
130
- onDisk.add(f);
131
- }
132
- }
133
-
134
- const undeclared = [...onDisk].filter(f => !declared.has(f))
135
- .map(f => path.relative(skillDir, f));
136
- const missing = [...declared].filter(f => !onDisk.has(f))
137
- .map(f => path.relative(skillDir, f));
138
-
139
- const status = (undeclared.length + missing.length) > 0 ? 'drift' : 'ok';
140
- const findings = [
141
- ...undeclared.map(f => finding('undeclared', `on disk but not in files[]: ${f}`, { path: f })),
142
- ...missing.map(f => finding('missing', `in files[] but not on disk: ${f}`, { path: f })),
143
- ];
144
- return {
145
- axis: 'manifestEnforcement',
146
- axis_num: 1,
147
- status,
148
- findings,
149
- summary: `${undeclared.length} undeclared, ${missing.length} missing`,
150
- };
151
- }
152
-
153
- /**
154
- * Axis 2 (Reference graph) — resolves markdown links in SKILL.md (and
155
- * optionally reference files). Reports links that don't resolve to an
156
- * existing file. Only checks relative links (ignores http://, #anchors,
157
- * mailto:, ~/ home paths, template placeholders).
158
- *
159
- * By default only scans SKILL.md. Pass `includeRefs: true` to also scan
160
- * reference files (reference files often contain example paths that produce
161
- * false positives — use with judgment).
162
- *
163
- * @param {{ skillDir: string, skillMd: string, includeRefs?: boolean }} ctx
164
- */
165
- export function referenceGraph(ctx) {
166
- const { skillDir, skillMd, includeRefs = false } = ctx;
167
- const mdFiles = [skillMd];
168
- if (includeRefs) {
169
- mdFiles.push(...walkDir(path.join(skillDir, 'references'), { include: ['.md'] }));
170
- }
171
-
172
- const findings = [];
173
- for (const mdFile of mdFiles) {
174
- const content = readSafe(mdFile);
175
- const links = extractMarkdownLinks(content, mdFile);
176
- for (const { url, line, file } of links) {
177
- // Resolve relative to the containing .md file's directory
178
- const resolved = path.resolve(path.dirname(file), url);
179
- if (!fs.existsSync(resolved)) {
180
- const relFile = path.relative(skillDir, file);
181
- const relResolved = path.relative(skillDir, resolved);
182
- findings.push(finding('broken-link',
183
- `${relFile}:${line} → ${url} (resolves to ${relResolved}, not found)`,
184
- { file: relFile, line, url }
185
- ));
186
- }
187
- }
188
- }
189
-
190
- return {
191
- axis: 'referenceGraph',
192
- axis_num: 2,
193
- status: findings.length > 0 ? 'drift' : 'ok',
194
- findings,
195
- summary: `${findings.length} broken link(s)`,
196
- };
197
- }
198
-
199
- /**
200
- * Axis 3 (Capability-menu drift) — parses §ColdStartTriage table rows and
201
- * checks that each referenced entry-reference path exists on disk.
202
- * Handles both `[label](path)` link format and bare path format.
203
- *
204
- * @param {{ skillDir: string, skillMd: string }} ctx
205
- */
206
- export function capabilityMenuDrift(ctx) {
207
- const { skillDir, skillMd } = ctx;
208
- const content = readSafe(skillMd);
209
-
210
- // Find §ColdStartTriage section
211
- const sectionMatch = content.match(
212
- /##\s+§ColdStartTriage[\s\S]*?(?=\n##\s+|$)/
213
- );
214
- if (!sectionMatch) {
215
- return {
216
- axis: 'capabilityMenuDrift',
217
- axis_num: 3,
218
- status: 'ok',
219
- findings: [],
220
- summary: 'No §ColdStartTriage section found (skip)',
221
- };
222
- }
223
-
224
- const section = sectionMatch[0];
225
- // Extract all markdown links within the section
226
- const links = extractMarkdownLinks(section, skillMd);
227
- // Also look for bare `references/X.md` patterns in the section (not in links)
228
- const barePaths = [...section.matchAll(/`(references\/[^`]+\.md)`/g)]
229
- .map(m => m[1]);
230
-
231
- const findings = [];
232
-
233
- for (const { url, text } of links) {
234
- if (!url.startsWith('references/') && !url.startsWith('../')) continue;
235
- const resolved = path.resolve(skillDir, url);
236
- if (!fs.existsSync(resolved)) {
237
- findings.push(finding('missing-entry-ref',
238
- `Mode entry ref missing: [${text}](${url})`,
239
- { url }
240
- ));
241
- }
242
- }
243
-
244
- for (const p of barePaths) {
245
- const resolved = path.join(skillDir, p);
246
- if (!fs.existsSync(resolved)) {
247
- findings.push(finding('missing-entry-ref',
248
- `Bare path in §ColdStartTriage not found: ${p}`,
249
- { url: p }
250
- ));
251
- }
252
- }
253
-
254
- return {
255
- axis: 'capabilityMenuDrift',
256
- axis_num: 3,
257
- status: findings.length > 0 ? 'drift' : 'ok',
258
- findings,
259
- summary: `${findings.length} missing entry-reference path(s)`,
260
- };
261
- }
262
-
263
- /**
264
- * Axis 4 (Version-literal parity) — checks that SKILL.md frontmatter version
265
- * matches skill.json version. Also scans the §Status section body for stale
266
- * version literals that don't match.
267
- *
268
- * @param {{ skillDir: string, skillMd: string, skillJson: string }} ctx
269
- */
270
- export function versionLiteralParity(ctx) {
271
- const { skillMd, skillJson } = ctx;
272
- const content = readSafe(skillMd);
273
- const pkg = JSON.parse(readSafe(skillJson) || '{}');
274
- const jsonVersion = pkg.version || null;
275
-
276
- const findings = [];
277
-
278
- if (!jsonVersion) {
279
- findings.push(finding('no-version', 'skill.json has no version field'));
280
- return { axis: 'versionLiteralParity', axis_num: 4, status: 'drift', findings, summary: 'no version in skill.json' };
281
- }
282
-
283
- // Check frontmatter version
284
- const fm = parseFrontmatter(content);
285
- if (fm.version && fm.version !== jsonVersion) {
286
- findings.push(finding('frontmatter-drift',
287
- `SKILL.md frontmatter version: ${fm.version} ≠ skill.json: ${jsonVersion}`
288
- ));
289
- }
290
-
291
- // Scan §Status section body for "Version X.Y.Z" or "vX.Y.Z" stale mentions
292
- const statusMatch = content.match(/##\s+§Status([\s\S]*?)(?=\n##\s+|$)/);
293
- if (statusMatch) {
294
- const statusBody = statusMatch[1];
295
- // Find any version that looks like a semver but isn't the current one
296
- const versionRe = /\bv?(\d+\.\d+\.\d+)\b/g;
297
- let m;
298
- while ((m = versionRe.exec(statusBody)) !== null) {
299
- const found = m[1];
300
- if (found !== jsonVersion) {
301
- findings.push(finding('status-section-stale',
302
- `§Status body contains version ${found} ≠ skill.json ${jsonVersion} — §Status should only point at CHANGELOG.md`
303
- ));
304
- }
305
- }
306
- }
307
-
308
- return {
309
- axis: 'versionLiteralParity',
310
- axis_num: 4,
311
- status: findings.length > 0 ? 'drift' : 'ok',
312
- findings,
313
- summary: findings.length > 0 ? `${findings.length} version-literal mismatch(es)` : `all literals match ${jsonVersion}`,
314
- };
315
- }
316
-
317
- /**
318
- * Axis 5 (Phase-label absence) — searches SKILL.md body for stale phase
319
- * annotations: "(Phase 1)", "(Phase 2 — planned)", "Phase 4 (v1.0.0, this)",
320
- * etc. These should have been removed as part of Round 0 cleanup.
321
- *
322
- * @param {{ skillMd: string }} ctx
323
- */
324
- export function phaseLabelAbsence(ctx) {
325
- const { skillMd } = ctx;
326
- const content = readSafe(skillMd);
327
- const lines = content.split('\n');
328
-
329
- const findings = [];
330
- const phaseRe = /\(Phase\s+\d+[\w\s—.]*\)|Phase\s+\d+\s*\(v\d+\.\d+\.\d+|Phase\s+\d+\s+references\s*\(planned\)/i;
331
-
332
- for (let i = 0; i < lines.length; i++) {
333
- if (phaseRe.test(lines[i])) {
334
- findings.push(finding('phase-label',
335
- `Line ${i + 1}: stale phase annotation — ${lines[i].trim()}`,
336
- { line: i + 1, content: lines[i].trim() }
337
- ));
338
- }
339
- }
340
-
341
- return {
342
- axis: 'phaseLabelAbsence',
343
- axis_num: 5,
344
- status: findings.length > 0 ? 'drift' : 'ok',
345
- findings,
346
- summary: `${findings.length} stale phase label(s)`,
347
- };
348
- }
349
-
350
- /**
351
- * Axis 6 (Fence-leak detection) — scans SKILL.md for H2/H3 headings inside
352
- * fenced code blocks that are NOT ``` markdown / md / mdx ``` type.
353
- * These are typically fence-close mistakes where a heading leaked into
354
- * a code block.
355
- *
356
- * @param {{ skillMd: string }} ctx
357
- */
358
- export function fenceLeakDetection(ctx) {
359
- const { skillMd } = ctx;
360
- const content = readSafe(skillMd);
361
- const lines = content.split('\n');
362
-
363
- const findings = [];
364
- let inFence = false;
365
- let fenceLang = '';
366
- let fenceStart = 0;
367
-
368
- for (let i = 0; i < lines.length; i++) {
369
- const line = lines[i];
370
- const fenceOpen = line.match(/^```(\w*)/);
371
-
372
- if (fenceOpen && !inFence) {
373
- inFence = true;
374
- fenceLang = fenceOpen[1].toLowerCase();
375
- fenceStart = i + 1;
376
- continue;
377
- }
378
-
379
- if (inFence && line.startsWith('```') && !line.startsWith('````')) {
380
- inFence = false;
381
- fenceLang = '';
382
- continue;
383
- }
384
-
385
- if (inFence) {
386
- // Headings inside markdown fences are intentional (templates)
387
- if (['markdown', 'md', 'mdx'].includes(fenceLang)) continue;
388
- // Headings inside non-markdown fences are likely fence-leak bugs
389
- if (/^#{2,3}\s/.test(line)) {
390
- findings.push(finding('fence-leak',
391
- `Line ${i + 1}: heading inside \`\`\`${fenceLang}\`\`\` fence — ${line.trim()}`,
392
- { line: i + 1, fenceLang, content: line.trim() }
393
- ));
394
- }
395
- }
396
- }
397
-
398
- return {
399
- axis: 'fenceLeakDetection',
400
- axis_num: 6,
401
- status: findings.length > 0 ? 'drift' : 'ok',
402
- findings,
403
- summary: `${findings.length} fence-leak heading(s)`,
404
- };
405
- }
406
-
407
- /**
408
- * Axis 7 (Content currency) — verifies that paths in skill.json files[]
409
- * actually exist. A missing file means the manifest is stale (probably a
410
- * file was moved or deleted without updating the manifest).
411
- * Note: this overlaps with manifestEnforcement's "declared-but-missing"
412
- * direction; use that for bidirectional checks, this for declaration-only.
413
- *
414
- * @param {{ skillDir: string, skillJson: string }} ctx
415
- */
416
- export function contentCurrency(ctx) {
417
- const { skillDir, skillJson } = ctx;
418
- const pkg = JSON.parse(readSafe(skillJson) || '{}');
419
- const declared = pkg.files || [];
420
-
421
- const findings = [];
422
- for (const relPath of declared) {
423
- const full = path.resolve(skillDir, relPath);
424
- if (!fs.existsSync(full)) {
425
- findings.push(finding('missing-declared',
426
- `Declared in files[] but not on disk: ${relPath}`,
427
- { path: relPath }
428
- ));
429
- }
430
- }
431
-
432
- return {
433
- axis: 'contentCurrency',
434
- axis_num: 7,
435
- status: findings.length > 0 ? 'drift' : 'ok',
436
- findings,
437
- summary: `${findings.length} declared-but-missing file(s)`,
438
- };
439
- }
440
-
441
- /**
442
- * Axis 8 (CLI helper currency) — scans SKILL.md for `npm run X` and
443
- * `node scripts/Y` patterns and checks they resolve. Scripts labeled
444
- * `(substrate-only)` or `(monorepo only)` are skipped.
445
- * Requires repoRoot to resolve package.json for npm run checks.
446
- *
447
- * @param {{ skillDir: string, skillMd: string, repoRoot?: string }} ctx
448
- */
449
- export function cliHelperCurrency(ctx) {
450
- const { skillDir, skillMd, repoRoot } = ctx;
451
- const content = readSafe(skillMd);
452
-
453
- const findings = [];
454
-
455
- // Parse package.json scripts if repoRoot provided
456
- let pkgScripts = null;
457
- if (repoRoot) {
458
- try {
459
- const pkgJson = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
460
- pkgScripts = pkgJson.scripts || {};
461
- } catch { /* repoRoot doesn't have package.json */ }
462
- }
463
-
464
- // Skip lines with (substrate-only) or (monorepo only) annotations
465
- const lines = content.split('\n');
466
- for (let i = 0; i < lines.length; i++) {
467
- const line = lines[i];
468
- if (/(substrate.only|monorepo.only)/i.test(line)) continue;
469
- // Skip comment lines
470
- if (/^\s*(#|\/\/)/.test(line)) continue;
471
-
472
- // npm run X
473
- const npmRe = /`npm run ([\w:.-]+)`/g;
474
- let m;
475
- while ((m = npmRe.exec(line)) !== null) {
476
- const scriptName = m[1];
477
- if (pkgScripts && !pkgScripts[scriptName]) {
478
- findings.push(finding('missing-npm-script',
479
- `Line ${i + 1}: npm run ${scriptName} not found in package.json scripts`,
480
- { line: i + 1, script: scriptName }
481
- ));
482
- }
483
- }
484
-
485
- // node scripts/X.mjs or node skills/.../scripts/X.mjs
486
- const nodeRe = /`node\s+(skills\/[^\s`]+|scripts\/[^\s`]+)`/g;
487
- while ((m = nodeRe.exec(line)) !== null) {
488
- const scriptPath = m[1];
489
- // Resolve relative to repoRoot if available, otherwise skillDir
490
- const base = repoRoot || skillDir;
491
- const resolved = path.resolve(base, scriptPath);
492
- if (!fs.existsSync(resolved)) {
493
- findings.push(finding('missing-script',
494
- `Line ${i + 1}: node ${scriptPath} — file not found`,
495
- { line: i + 1, scriptPath }
496
- ));
497
- }
498
- }
499
- }
500
-
501
- return {
502
- axis: 'cliHelperCurrency',
503
- axis_num: 8,
504
- status: findings.length > 0 ? 'drift' : 'ok',
505
- findings,
506
- summary: `${findings.length} missing CLI helper(s)`,
507
- };
508
- }
509
-
510
- // ─── convenience: run a standard set ──────────────────────────────────────────
511
-
512
- /**
513
- * Run all 8 universal axes and return results.
514
- * Per-skill scripts call this then append their own axes.
515
- *
516
- * @param {{ skillDir: string, skillMd: string, skillJson: string, repoRoot?: string }} ctx
517
- * @returns {{ results: object[], driftCount: number }}
518
- */
519
- export function runUniversalAxes(ctx) {
520
- const axeFns = [
521
- manifestEnforcement,
522
- referenceGraph,
523
- capabilityMenuDrift,
524
- versionLiteralParity,
525
- phaseLabelAbsence,
526
- fenceLeakDetection,
527
- contentCurrency,
528
- cliHelperCurrency,
529
- ];
530
-
531
- const results = axeFns.map(fn => fn(ctx));
532
- const driftCount = results.filter(r => r.status === 'drift').length;
533
- return { results, driftCount };
534
- }
535
-
536
- /**
537
- * Format a results array as human-readable text.
538
- * @param {object[]} results
539
- * @param {{ strict?: boolean, json?: boolean }} opts
540
- */
541
- export function formatResults(results, opts = {}) {
542
- if (opts.json) return JSON.stringify({ results }, null, 2);
543
-
544
- const lines = [];
545
- for (const r of results) {
546
- const icon = r.status === 'ok' ? '✓' : '✗';
547
- lines.push(`${icon} ${r.axis}: ${r.summary}`);
548
- if (r.status === 'drift' && r.findings.length > 0) {
549
- for (const f of r.findings) {
550
- lines.push(` ${f.type}: ${f.message}`);
551
- }
552
- }
553
- }
554
- return lines.join('\n');
555
- }