@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,487 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * run-skill-evals.mjs — Portable eval runner for rollup-family skills.
4
- *
5
- * Walks a skill's evals/ directory and scores each corpus type:
6
- * routing-corpus.json — heuristic routing accuracy (F1 score)
7
- * adversarial-*.json — structural validation + case count
8
- * teach-routing-cases.json — branch routing eval (live or structural)
9
- * evals.json — structural validation + category counts
10
- *
11
- * Usage (from repo root):
12
- * node ${CLAUDE_PLUGIN_ROOT}/bin/lib/run-skill-evals.mjs --skill=adia-ui-kit
13
- * node ${CLAUDE_PLUGIN_ROOT}/bin/lib/run-skill-evals.mjs --skill=all
14
- * node ${CLAUDE_PLUGIN_ROOT}/bin/lib/run-skill-evals.mjs --skill=adia-ui-kit --corpus=routing
15
- * node ${CLAUDE_PLUGIN_ROOT}/bin/lib/run-skill-evals.mjs --skill=adia-ui-kit --json
16
- * node ${CLAUDE_PLUGIN_ROOT}/bin/lib/run-skill-evals.mjs --skill=adia-ui-kit --strict
17
- *
18
- * Exit code: 0 = all pass (or warnings only), 1 = failures (in --strict mode
19
- * or when hard-failure thresholds are missed).
20
- *
21
- * Scoring note: routing scores are heuristic (keyword overlap, not real LLM
22
- * routing). Treat as a structural signal and vocabulary-drift detector — not
23
- * ground truth for harness routing accuracy.
24
- *
25
- * @module run-skill-evals
26
- */
27
-
28
- import fs from 'node:fs';
29
- import path from 'node:path';
30
- import { spawnSync } from 'node:child_process';
31
- import process from 'node:process';
32
-
33
- // ─── constants ──────────────────────────────────────────────────────────────
34
-
35
- const REPO = process.cwd();
36
- const SKILLS_DIR = path.join(REPO, 'skills');
37
-
38
- // Routing heuristic thresholds
39
- const ROUTING_F1_PASS = 0.70;
40
- const ROUTING_F1_WARN = 0.85;
41
-
42
- // Stopwords excluded from token overlap
43
- const STOPWORDS = new Set([
44
- 'a', 'an', 'the', 'and', 'or', 'of', 'in', 'to', 'for', 'on', 'at',
45
- 'is', 'are', 'was', 'with', 'this', 'that', 'it', 'its', 'be', 'as',
46
- 'by', 'not', 'do', 'use', 'how', 'what', 'when', 'which', 'from',
47
- 'into', 'about', 'after', 'over', 'can', 'has', 'have', 'all', 'any',
48
- ]);
49
-
50
- // ─── helpers ────────────────────────────────────────────────────────────────
51
-
52
- function readJson(p) {
53
- try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch { return null; }
54
- }
55
-
56
- function tokenize(text) {
57
- return text.toLowerCase().split(/\W+/).filter(w => w.length > 2 && !STOPWORDS.has(w));
58
- }
59
-
60
- /** Extract quoted trigger phrases from a skill description. */
61
- function extractTriggerPhrases(description) {
62
- const triggers = [];
63
- // "Triggers on ..." section — extract quoted phrases
64
- const triggerBlock = description.match(/Triggers on\s+([\s\S]*?)(?:\.|Does NOT trigger|$)/i);
65
- if (triggerBlock) {
66
- const quotes = triggerBlock[1].matchAll(/"([^"]+)"/g);
67
- for (const m of quotes) triggers.push(m[1]);
68
- }
69
- return triggers;
70
- }
71
-
72
- /** Compute heuristic routing score for a phrase against a skill. */
73
- function scorePhrase(phrase, descTokenSet, triggerPhrases) {
74
- // Explicit trigger phrase match → high-confidence positive
75
- for (const t of triggerPhrases) {
76
- if (phrase.toLowerCase().includes(t.toLowerCase())) return 1.0;
77
- }
78
- // Word overlap ratio: phrase tokens that appear in description vocabulary
79
- const phraseTokens = tokenize(phrase);
80
- if (phraseTokens.length === 0) return 0;
81
- const overlap = phraseTokens.filter(t => descTokenSet.has(t)).length;
82
- return overlap / phraseTokens.length;
83
- }
84
-
85
- /** Find calibrated threshold maximizing F1 on labeled data. */
86
- function calibrateThreshold(scoredPhrases) {
87
- // Extract unique score values as candidate thresholds
88
- const candidates = [...new Set(scoredPhrases.map(p => p.score))].sort((a, b) => a - b);
89
- let bestF1 = -1;
90
- let bestT = 0.15;
91
-
92
- for (const t of candidates) {
93
- const { f1 } = computeF1(scoredPhrases, t);
94
- if (f1 > bestF1) { bestF1 = f1; bestT = t; }
95
- }
96
- return { threshold: bestT, calibratedF1: bestF1 };
97
- }
98
-
99
- function computeF1(scoredPhrases, threshold) {
100
- let tp = 0, fp = 0, fn = 0, tn = 0;
101
- for (const { score, expected } of scoredPhrases) {
102
- const predicted = score >= threshold;
103
- if (predicted && expected) tp++;
104
- else if (predicted && !expected) fp++;
105
- else if (!predicted && expected) fn++;
106
- else tn++;
107
- }
108
- const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
109
- const recall = tp + fn > 0 ? tp / (tp + fn) : 0;
110
- const f1 = precision + recall > 0 ? 2 * precision * recall / (precision + recall) : 0;
111
- return { tp, fp, fn, tn, precision, recall, f1 };
112
- }
113
-
114
- // ─── corpus evaluators ──────────────────────────────────────────────────────
115
-
116
- /**
117
- * Evaluate routing-corpus.json: heuristic phrase classification + F1.
118
- */
119
- function evalRoutingCorpus(corpusPath, skillDir) {
120
- const corpus = readJson(corpusPath);
121
- if (!corpus) return { type: 'routing', status: 'error', message: 'Failed to parse corpus' };
122
-
123
- const phrases = corpus.phrases ?? [];
124
- if (phrases.length === 0) {
125
- return { type: 'routing', status: 'fail', message: 'No phrases found' };
126
- }
127
-
128
- // Detect label field (e.g. should_route_to_kit, should_route_to_ops)
129
- const firstPhrase = phrases[0];
130
- const labelField = Object.keys(firstPhrase).find(k => k.startsWith('should_route_to_'));
131
- if (!labelField) {
132
- return { type: 'routing', status: 'fail', message: 'No should_route_to_* field found in phrases' };
133
- }
134
-
135
- // Load skill description for heuristic scoring
136
- const skillJson = readJson(path.join(skillDir, 'skill.json')) ?? {};
137
- const description = skillJson.description ?? '';
138
- const descTokenSet = new Set(tokenize(description));
139
- const triggerPhrases = extractTriggerPhrases(description);
140
-
141
- // Score each phrase
142
- const scoredPhrases = phrases.map(p => ({
143
- id: p.id,
144
- phrase: p.phrase,
145
- score: scorePhrase(p.phrase, descTokenSet, triggerPhrases),
146
- expected: Boolean(p[labelField]),
147
- expected_alternative: p.expected_alternative ?? null,
148
- }));
149
-
150
- const positives = scoredPhrases.filter(p => p.expected);
151
- const adversarials = scoredPhrases.filter(p => !p.expected);
152
-
153
- // Calibrate threshold on labeled data
154
- const { threshold, calibratedF1 } = calibrateThreshold(scoredPhrases);
155
- const { tp, fp, fn, tn, precision, recall, f1 } = computeF1(scoredPhrases, threshold);
156
-
157
- // Check minimums
158
- const mins = corpus.minimums_per_spec ?? {};
159
- const minTrigger = mins.trigger_phrases ?? 10;
160
- const minAdversarial = mins.adversarial_phrases ?? 5;
161
- const minimumsMet = positives.length >= minTrigger && adversarials.length >= minAdversarial;
162
-
163
- const status = !minimumsMet ? 'fail'
164
- : f1 < ROUTING_F1_PASS ? 'fail'
165
- : f1 < ROUTING_F1_WARN ? 'warn'
166
- : 'pass';
167
-
168
- // Per-case results (only report misclassifications)
169
- const misclassified = scoredPhrases.filter(p => (p.score >= threshold) !== p.expected);
170
-
171
- return {
172
- type: 'routing',
173
- file: path.relative(skillDir, corpusPath),
174
- status,
175
- label_field: labelField,
176
- counts: { total: phrases.length, positives: positives.length, adversarials: adversarials.length },
177
- minimums: { trigger_phrases: { required: minTrigger, found: positives.length, ok: positives.length >= minTrigger },
178
- adversarial_phrases: { required: minAdversarial, found: adversarials.length, ok: adversarials.length >= minAdversarial } },
179
- heuristic: { threshold: +threshold.toFixed(3), trigger_phrases_extracted: triggerPhrases.length },
180
- scores: { tp, fp, fn, tn, precision: +precision.toFixed(3), recall: +recall.toFixed(3), f1: +f1.toFixed(3) },
181
- misclassified: misclassified.map(p => ({
182
- id: p.id, phrase: p.phrase, expected: p.expected, score: +p.score.toFixed(3),
183
- })),
184
- summary: `F1=${f1.toFixed(2)} (${tp} TP, ${fp} FP, ${fn} FN, ${tn} TN), ${positives.length}/${minTrigger} trigger, ${adversarials.length}/${minAdversarial} adversarial`,
185
- };
186
- }
187
-
188
- /**
189
- * Evaluate adversarial-*.json: structural validation + severity breakdown.
190
- */
191
- function evalAdversarialCorpus(corpusPath, skillDir) {
192
- const corpus = readJson(corpusPath);
193
- if (!corpus) return { type: 'adversarial', status: 'error', message: 'Failed to parse corpus' };
194
-
195
- const cases = corpus.cases ?? corpus.phrases ?? [];
196
- const required = ['id', 'expected_behavior'];
197
- const invalid = cases.filter(c => required.some(f => !c[f]));
198
-
199
- const severityCounts = {};
200
- for (const c of cases) {
201
- const sev = c.severity ?? 'unknown';
202
- severityCounts[sev] = (severityCounts[sev] ?? 0) + 1;
203
- }
204
-
205
- const status = cases.length < 3 ? 'warn'
206
- : invalid.length > 0 ? 'warn'
207
- : 'pass';
208
-
209
- return {
210
- type: 'adversarial',
211
- file: path.relative(skillDir, corpusPath),
212
- status,
213
- counts: { total: cases.length, invalid: invalid.length },
214
- severity_breakdown: severityCounts,
215
- invalid_ids: invalid.map(c => c.id ?? '(no id)'),
216
- summary: `${cases.length} cases, severity: ${Object.entries(severityCounts).map(([k, v]) => `${v} ${k}`).join(', ')}`,
217
- };
218
- }
219
-
220
- /**
221
- * Evaluate teach-routing-cases.json: branch coverage + live routing if
222
- * scripts/teach-route.mjs exists in the skill directory.
223
- */
224
- async function evalTeachRoutingCorpus(corpusPath, skillDir) {
225
- const corpus = readJson(corpusPath);
226
- if (!corpus) return { type: 'teach-routing', status: 'error', message: 'Failed to parse corpus' };
227
-
228
- const cases = corpus.cases ?? [];
229
- const mins = corpus.minimums_per_spec ?? {};
230
- const minTotal = mins.total_cases ?? 7;
231
-
232
- // Check branch coverage
233
- const branchCoverage = {};
234
- for (const c of cases) {
235
- const b = c.expected_branch ?? '?';
236
- branchCoverage[b] = (branchCoverage[b] ?? 0) + 1;
237
- }
238
-
239
- const teachScript = path.join(skillDir, 'scripts', 'teach-route.mjs');
240
- const scriptExists = fs.existsSync(teachScript);
241
-
242
- if (!scriptExists) {
243
- // Structural-only evaluation
244
- const status = cases.length < minTotal ? 'warn' : 'pass';
245
- return {
246
- type: 'teach-routing',
247
- file: path.relative(skillDir, corpusPath),
248
- status,
249
- live_run: false,
250
- counts: { total: cases.length, required: minTotal },
251
- branch_coverage: branchCoverage,
252
- summary: `${cases.length} cases, branches: ${Object.keys(branchCoverage).sort().join(',')} — teach-route.mjs not yet authored (pending Round 3)`,
253
- };
254
- }
255
-
256
- // Live routing evaluation: run teach-route.mjs --eval=<payload> for each case
257
- let passed = 0;
258
- let failed = 0;
259
- const results = [];
260
-
261
- for (const c of cases) {
262
- // Pass --json so teach-route.mjs emits structured output regardless of format
263
- const proc = spawnSync(process.execPath, [teachScript, `--payload=${c.payload}`, '--json'], {
264
- cwd: REPO, encoding: 'utf8', timeout: 5000,
265
- });
266
- const output = (proc.stdout ?? '').trim();
267
- let gotBranch = null;
268
- try {
269
- const parsed = JSON.parse(output);
270
- gotBranch = parsed.branch ?? parsed.id ?? null;
271
- } catch {
272
- // Fall back to parsing text output: " branch: A (name)" or leading capital
273
- const textMatch = output.match(/branch:\s*([A-Z])(?:\s|$)/m);
274
- gotBranch = textMatch?.[1] ?? output.match(/^([A-Z])\b/)?.[1] ?? null;
275
- }
276
- const ok = gotBranch === c.expected_branch;
277
- if (ok) passed++; else failed++;
278
- results.push({
279
- id: c.id, expected: c.expected_branch, got: gotBranch,
280
- status: ok ? 'pass' : 'fail',
281
- ...(!ok ? { failure_reason: `expected '${c.expected_branch}', got '${gotBranch}'` } : {}),
282
- });
283
- }
284
-
285
- const f1 = cases.length > 0 ? passed / cases.length : 0;
286
- const status = failed > 0 ? 'fail' : cases.length < minTotal ? 'warn' : 'pass';
287
-
288
- return {
289
- type: 'teach-routing',
290
- file: path.relative(skillDir, corpusPath),
291
- status,
292
- live_run: true,
293
- counts: { total: cases.length, passed, failed, required: minTotal },
294
- branch_coverage: branchCoverage,
295
- results,
296
- summary: `${passed}/${cases.length} passed (live routing via teach-route.mjs)`,
297
- };
298
- }
299
-
300
- /**
301
- * Evaluate evals.json: structural validation + category counts.
302
- * (Full behavioral execution requires an LLM — not run here.)
303
- */
304
- function evalEvalsJson(corpusPath, skillDir) {
305
- const corpus = readJson(corpusPath);
306
- if (!corpus) return { type: 'evals', status: 'error', message: 'Failed to parse corpus' };
307
-
308
- const evals = corpus.evals ?? [];
309
- const invariants = corpus.non_eval_invariants ?? corpus.invariants ?? [];
310
-
311
- // Count by category
312
- const categoryCounts = {};
313
- for (const e of evals) {
314
- const cat = e.category ?? 'uncategorized';
315
- categoryCounts[cat] = (categoryCounts[cat] ?? 0) + 1;
316
- }
317
-
318
- const status = evals.length === 0 ? 'warn' : 'pass';
319
-
320
- return {
321
- type: 'evals',
322
- file: path.relative(skillDir, corpusPath),
323
- status,
324
- counts: { evals: evals.length, invariants: invariants.length },
325
- categories: categoryCounts,
326
- note: 'Structural check only — behavioral execution requires LLM',
327
- summary: `${evals.length} evals (${Object.entries(categoryCounts).map(([k, v]) => `${v} ${k}`).join(', ')}), ${invariants.length} invariants`,
328
- };
329
- }
330
-
331
- // ─── skill runner ────────────────────────────────────────────────────────────
332
-
333
- async function runSkillEvals(skillName, corpusFilter) {
334
- const skillDir = path.join(SKILLS_DIR, skillName);
335
- const evalsDir = path.join(skillDir, 'evals');
336
-
337
- if (!fs.existsSync(skillDir)) {
338
- return { skill: skillName, status: 'error', message: `Skill directory not found: ${skillDir}` };
339
- }
340
-
341
- if (!fs.existsSync(evalsDir)) {
342
- return { skill: skillName, status: 'warn', message: 'No evals/ directory', corpora: [] };
343
- }
344
-
345
- const files = fs.readdirSync(evalsDir).filter(f => f.endsWith('.json'));
346
- if (files.length === 0) {
347
- return { skill: skillName, status: 'warn', message: 'evals/ directory is empty', corpora: [] };
348
- }
349
-
350
- const corpora = [];
351
-
352
- for (const file of files.sort()) {
353
- const fullPath = path.join(evalsDir, file);
354
-
355
- // Detect corpus type and apply filter
356
- let type;
357
- if (file === 'routing-corpus.json') type = 'routing';
358
- else if (file === 'teach-routing-cases.json') type = 'teach';
359
- else if (file.startsWith('adversarial-')) type = 'adversarial';
360
- else if (file === 'evals.json') type = 'evals';
361
- else type = 'unknown';
362
-
363
- if (corpusFilter && corpusFilter !== 'all' && type !== corpusFilter) continue;
364
- if (type === 'unknown') {
365
- corpora.push({ type: 'unknown', file, status: 'skip', summary: 'Unrecognized corpus type' });
366
- continue;
367
- }
368
-
369
- let result;
370
- if (type === 'routing') result = evalRoutingCorpus(fullPath, skillDir);
371
- else if (type === 'adversarial') result = evalAdversarialCorpus(fullPath, skillDir);
372
- else if (type === 'teach') result = await evalTeachRoutingCorpus(fullPath, skillDir);
373
- else if (type === 'evals') result = evalEvalsJson(fullPath, skillDir);
374
-
375
- corpora.push(result);
376
- }
377
-
378
- const statuses = corpora.map(c => c.status);
379
- const overallStatus = statuses.includes('error') ? 'error'
380
- : statuses.includes('fail') ? 'fail'
381
- : statuses.includes('warn') ? 'warn'
382
- : 'pass';
383
-
384
- return { skill: skillName, status: overallStatus, corpora };
385
- }
386
-
387
- // ─── formatting ──────────────────────────────────────────────────────────────
388
-
389
- function icon(status) {
390
- return { pass: '✓', warn: '⚠', fail: '✗', error: '!', skip: '·' }[status] ?? '?';
391
- }
392
-
393
- function formatSkillResult(result) {
394
- const lines = [`[${result.skill}] ${icon(result.status)} ${result.status.toUpperCase()}`];
395
-
396
- if (result.message) {
397
- lines.push(` ${result.message}`);
398
- }
399
-
400
- for (const corpus of result.corpora ?? []) {
401
- const ind = icon(corpus.status);
402
- lines.push(` ${ind} ${corpus.file ?? corpus.type}: ${corpus.summary ?? corpus.message ?? ''}`);
403
-
404
- if (corpus.status === 'fail' || corpus.status === 'warn') {
405
- if (corpus.misclassified?.length) {
406
- lines.push(` Misclassified (${corpus.misclassified.length}):`);
407
- for (const m of corpus.misclassified.slice(0, 5)) {
408
- lines.push(` [${m.id}] score=${m.score} expected=${m.expected} — "${m.phrase}"`);
409
- }
410
- if (corpus.misclassified.length > 5) {
411
- lines.push(` ... ${corpus.misclassified.length - 5} more`);
412
- }
413
- }
414
- if (corpus.invalid_ids?.length) {
415
- lines.push(` Invalid cases: ${corpus.invalid_ids.join(', ')}`);
416
- }
417
- if (corpus.results?.filter(r => r.status === 'fail').length) {
418
- for (const r of corpus.results.filter(r => r.status === 'fail')) {
419
- lines.push(` [${r.id}] ${r.failure_reason}`);
420
- }
421
- }
422
- if (corpus.minimums) {
423
- for (const [k, v] of Object.entries(corpus.minimums)) {
424
- if (!v.ok) lines.push(` Minimum not met: ${k} requires ${v.required}, found ${v.found}`);
425
- }
426
- }
427
- }
428
- }
429
-
430
- return lines.join('\n');
431
- }
432
-
433
- // ─── main ────────────────────────────────────────────────────────────────────
434
-
435
- async function main() {
436
- const rawArgs = process.argv.slice(2);
437
- const args = Object.fromEntries(
438
- rawArgs.filter(a => a.startsWith('--')).map(a => {
439
- const [k, ...v] = a.slice(2).split('=');
440
- return [k, v.length ? v.join('=') : true];
441
- })
442
- );
443
-
444
- const { skill: skillArg = 'all', corpus: corpusFilter, json: jsonMode, strict } = args;
445
-
446
- // Discover skills to run
447
- let skillNames;
448
- if (skillArg === 'all') {
449
- skillNames = fs.readdirSync(SKILLS_DIR).filter(name => {
450
- const d = path.join(SKILLS_DIR, name);
451
- return fs.statSync(d).isDirectory() && fs.existsSync(path.join(d, 'SKILL.md'));
452
- }).sort();
453
- } else {
454
- skillNames = [skillArg];
455
- }
456
-
457
- const results = [];
458
- for (const name of skillNames) {
459
- results.push(await runSkillEvals(name, corpusFilter ?? 'all'));
460
- }
461
-
462
- if (jsonMode) {
463
- const driftCount = results.filter(r => r.status === 'fail' || r.status === 'error').length;
464
- console.log(JSON.stringify({ results, driftCount }, null, 2));
465
- } else {
466
- const hasEvals = results.filter(r => r.corpora?.length > 0 || r.status === 'warn');
467
- const noEvals = results.filter(r => !r.corpora?.length && r.status !== 'warn' && r.message?.includes('No evals'));
468
-
469
- for (const r of hasEvals) console.log(formatSkillResult(r));
470
-
471
- if (noEvals.length && !corpusFilter) {
472
- console.log(`\n (${noEvals.length} skill(s) have no evals/ yet: ${noEvals.map(r => r.skill).join(', ')})`);
473
- }
474
-
475
- const passed = results.filter(r => r.status === 'pass').length;
476
- const warned = results.filter(r => r.status === 'warn').length;
477
- const failed = results.filter(r => r.status === 'fail' || r.status === 'error').length;
478
- const withEvals = results.filter(r => r.corpora?.length > 0).length;
479
- console.log(`\n${withEvals} skill(s) with evals: ${passed} pass, ${warned} warn, ${failed} fail`);
480
- }
481
-
482
- const anyFail = results.some(r => r.status === 'fail' || r.status === 'error');
483
- const anyWarn = results.some(r => r.status === 'warn');
484
- if (anyFail || (strict && anyWarn)) process.exit(1);
485
- }
486
-
487
- main().catch(err => { console.error(err); process.exit(2); });