bmad-module-skill-forge 1.9.0 → 2.0.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 (281) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/README.md +10 -5
  4. package/docs/_data/pinned.yaml +2 -2
  5. package/docs/agents.md +11 -2
  6. package/docs/architecture.md +5 -4
  7. package/docs/bmad-synergy.md +30 -7
  8. package/docs/campaign.md +172 -0
  9. package/docs/examples.md +7 -3
  10. package/docs/forge-auto.md +90 -0
  11. package/docs/getting-started.md +9 -0
  12. package/docs/how-it-works.md +6 -4
  13. package/docs/index.md +4 -3
  14. package/docs/skill-model.md +1 -1
  15. package/docs/troubleshooting.md +17 -1
  16. package/docs/verifying-a-skill.md +2 -2
  17. package/docs/why-skf.md +1 -1
  18. package/docs/workflows.md +64 -24
  19. package/package.json +2 -2
  20. package/src/knowledge/skill-lifecycle.md +1 -1
  21. package/src/module-help.csv +4 -3
  22. package/src/shared/data/language-corpora.json +32 -0
  23. package/src/shared/references/pipeline-contracts.md +6 -3
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-comention-pairs.py +350 -0
  26. package/src/shared/scripts/skf-count-tokens.py +222 -0
  27. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  28. package/src/shared/scripts/skf-detect-docs.py +240 -4
  29. package/src/shared/scripts/skf-detect-language.py +82 -3
  30. package/src/shared/scripts/skf-detect-tools.py +105 -2
  31. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  32. package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
  33. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  34. package/src/shared/scripts/skf-find-cycles.py +246 -0
  35. package/src/shared/scripts/skf-hash-content.py +487 -2
  36. package/src/shared/scripts/skf-language-corpora.py +100 -0
  37. package/src/shared/scripts/skf-manifest-ops.py +67 -1
  38. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  39. package/src/shared/scripts/skf-preapply.py +1 -1
  40. package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
  41. package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
  42. package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
  43. package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
  44. package/src/shared/scripts/skf-shape-detect.py +576 -25
  45. package/src/shared/scripts/skf-shard-body.py +461 -0
  46. package/src/shared/scripts/skf-skill-inventory.py +139 -3
  47. package/src/shared/scripts/skf-structural-diff.py +307 -67
  48. package/src/shared/scripts/skf-validate-brief-schema.py +41 -6
  49. package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
  50. package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
  51. package/src/shared/scripts/skf-validate-output.py +443 -10
  52. package/src/shared/scripts/skf-validate-pins.py +3 -3
  53. package/src/shared/scripts/skf-verify-no-trace.py +214 -0
  54. package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
  55. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  56. package/src/skf-analyze-source/SKILL.md +17 -32
  57. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  58. package/src/skf-analyze-source/customize.toml +2 -0
  59. package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
  60. package/src/skf-analyze-source/references/continue.md +8 -13
  61. package/src/skf-analyze-source/references/generate-briefs.md +28 -12
  62. package/src/skf-analyze-source/references/headless-contract.md +33 -0
  63. package/src/skf-analyze-source/references/health-check.md +1 -2
  64. package/src/skf-analyze-source/references/identify-units.md +25 -36
  65. package/src/skf-analyze-source/references/init.md +12 -25
  66. package/src/skf-analyze-source/references/map-and-detect.md +8 -20
  67. package/src/skf-analyze-source/references/recommend.md +4 -14
  68. package/src/skf-analyze-source/references/scan-project.md +3 -11
  69. package/src/skf-analyze-source/references/step-auto-scope.md +211 -197
  70. package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
  71. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
  72. package/src/skf-audit-skill/SKILL.md +9 -6
  73. package/src/skf-audit-skill/customize.toml +2 -0
  74. package/src/skf-audit-skill/references/init.md +58 -32
  75. package/src/skf-audit-skill/references/re-index.md +15 -34
  76. package/src/skf-audit-skill/references/report.md +11 -58
  77. package/src/skf-audit-skill/references/semantic-diff.md +4 -22
  78. package/src/skf-audit-skill/references/severity-classify.md +53 -87
  79. package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
  80. package/src/skf-audit-skill/references/structural-diff.md +43 -68
  81. package/src/skf-brief-skill/SKILL.md +7 -36
  82. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  83. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  84. package/src/skf-brief-skill/customize.toml +14 -0
  85. package/src/skf-brief-skill/references/analyze-target.md +13 -18
  86. package/src/skf-brief-skill/references/confirm-brief.md +2 -15
  87. package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
  88. package/src/skf-brief-skill/references/gather-intent.md +27 -24
  89. package/src/skf-brief-skill/references/headless-args.md +1 -1
  90. package/src/skf-brief-skill/references/health-check.md +1 -22
  91. package/src/skf-brief-skill/references/invocation-contract.md +37 -0
  92. package/src/skf-brief-skill/references/scope-definition.md +6 -15
  93. package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
  94. package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
  95. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  96. package/src/skf-brief-skill/references/write-brief.md +19 -13
  97. package/src/skf-campaign/SKILL.md +121 -0
  98. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  99. package/src/skf-campaign/customize.toml +75 -0
  100. package/src/skf-campaign/manifest.yaml +15 -0
  101. package/src/skf-campaign/references/campaign-contracts.md +67 -0
  102. package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
  103. package/src/skf-campaign/references/health-check.md +35 -0
  104. package/src/skf-campaign/references/step-01-setup.md +121 -0
  105. package/src/skf-campaign/references/step-02-strategy.md +102 -0
  106. package/src/skf-campaign/references/step-03-pins.md +56 -0
  107. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  108. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  109. package/src/skf-campaign/references/step-06-batch.md +88 -0
  110. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  111. package/src/skf-campaign/references/step-08-verify.md +75 -0
  112. package/src/skf-campaign/references/step-09-refine.md +83 -0
  113. package/src/skf-campaign/references/step-10-export.md +106 -0
  114. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  115. package/src/skf-campaign/references/step-resume.md +115 -0
  116. package/src/skf-campaign/scripts/.gitkeep +0 -0
  117. package/src/skf-campaign/scripts/campaign-deps.py +244 -0
  118. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  119. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  120. package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
  121. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  122. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  123. package/src/skf-campaign/scripts/campaign-status.py +162 -0
  124. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  125. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  126. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  127. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  128. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  129. package/src/skf-create-skill/SKILL.md +15 -4
  130. package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
  131. package/src/skf-create-skill/assets/skill-sections.md +9 -100
  132. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
  133. package/src/skf-create-skill/customize.toml +13 -0
  134. package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
  135. package/src/skf-create-skill/references/compile.md +37 -47
  136. package/src/skf-create-skill/references/component-extraction.md +8 -35
  137. package/src/skf-create-skill/references/ecosystem-check.md +9 -25
  138. package/src/skf-create-skill/references/enrich.md +4 -17
  139. package/src/skf-create-skill/references/extract.md +20 -27
  140. package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
  141. package/src/skf-create-skill/references/extraction-patterns.md +10 -8
  142. package/src/skf-create-skill/references/generate-artifacts.md +20 -25
  143. package/src/skf-create-skill/references/health-check.md +2 -2
  144. package/src/skf-create-skill/references/load-brief.md +16 -18
  145. package/src/skf-create-skill/references/report.md +27 -14
  146. package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
  147. package/src/skf-create-skill/references/step-auto-shard.md +52 -25
  148. package/src/skf-create-skill/references/step-doc-rot.md +33 -15
  149. package/src/skf-create-skill/references/step-doc-sources.md +13 -4
  150. package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
  151. package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
  152. package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
  153. package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
  154. package/src/skf-create-skill/references/validate.md +69 -29
  155. package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
  156. package/src/skf-create-stack-skill/SKILL.md +15 -9
  157. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
  158. package/src/skf-create-stack-skill/customize.toml +10 -0
  159. package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
  160. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
  161. package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
  162. package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
  163. package/src/skf-create-stack-skill/references/generate-output.md +24 -75
  164. package/src/skf-create-stack-skill/references/init.md +9 -81
  165. package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
  166. package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
  167. package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
  168. package/src/skf-create-stack-skill/references/report.md +27 -62
  169. package/src/skf-create-stack-skill/references/validate.md +35 -31
  170. package/src/skf-drop-skill/SKILL.md +20 -23
  171. package/src/skf-drop-skill/customize.toml +10 -0
  172. package/src/skf-drop-skill/references/execute.md +67 -36
  173. package/src/skf-drop-skill/references/headless-contract.md +18 -0
  174. package/src/skf-drop-skill/references/health-check.md +1 -13
  175. package/src/skf-drop-skill/references/report.md +44 -10
  176. package/src/skf-drop-skill/references/select.md +93 -48
  177. package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
  178. package/src/skf-export-skill/SKILL.md +17 -14
  179. package/src/skf-export-skill/assets/managed-section-format.md +2 -5
  180. package/src/skf-export-skill/customize.toml +10 -6
  181. package/src/skf-export-skill/references/generate-snippet.md +21 -35
  182. package/src/skf-export-skill/references/load-skill.md +44 -42
  183. package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
  184. package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
  185. package/src/skf-export-skill/references/package.md +27 -33
  186. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
  187. package/src/skf-export-skill/references/result-envelope.md +28 -0
  188. package/src/skf-export-skill/references/summary.md +11 -5
  189. package/src/skf-export-skill/references/token-report.md +42 -24
  190. package/src/skf-export-skill/references/update-context.md +32 -33
  191. package/src/skf-forger/SKILL.md +32 -39
  192. package/src/skf-forger/references/pipeline-mode.md +34 -0
  193. package/src/skf-forger/scripts/parse-pipeline.py +270 -0
  194. package/src/skf-quick-skill/SKILL.md +10 -60
  195. package/src/skf-quick-skill/customize.toml +9 -0
  196. package/src/skf-quick-skill/references/batch-mode.md +6 -4
  197. package/src/skf-quick-skill/references/compile.md +15 -52
  198. package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
  199. package/src/skf-quick-skill/references/finalize.md +12 -6
  200. package/src/skf-quick-skill/references/halt-contract.md +54 -0
  201. package/src/skf-quick-skill/references/health-check.md +3 -4
  202. package/src/skf-quick-skill/references/quick-extract.md +7 -18
  203. package/src/skf-quick-skill/references/registry-resolution.md +1 -23
  204. package/src/skf-quick-skill/references/resolve-target.md +27 -34
  205. package/src/skf-quick-skill/references/write-and-validate.md +10 -25
  206. package/src/skf-refine-architecture/SKILL.md +16 -8
  207. package/src/skf-refine-architecture/customize.toml +9 -0
  208. package/src/skf-refine-architecture/references/compile.md +10 -8
  209. package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
  210. package/src/skf-refine-architecture/references/health-check.md +1 -1
  211. package/src/skf-refine-architecture/references/improvements.md +9 -25
  212. package/src/skf-refine-architecture/references/init.md +9 -16
  213. package/src/skf-refine-architecture/references/issue-detection.md +10 -25
  214. package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
  215. package/src/skf-refine-architecture/references/report.md +14 -11
  216. package/src/skf-rename-skill/SKILL.md +11 -21
  217. package/src/skf-rename-skill/customize.toml +10 -0
  218. package/src/skf-rename-skill/references/execute.md +112 -82
  219. package/src/skf-rename-skill/references/exit-codes.md +12 -0
  220. package/src/skf-rename-skill/references/report.md +16 -8
  221. package/src/skf-rename-skill/references/select.md +69 -42
  222. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
  223. package/src/skf-setup/SKILL.md +21 -13
  224. package/src/skf-setup/customize.toml +10 -0
  225. package/src/skf-setup/references/auto-index.md +11 -10
  226. package/src/skf-setup/references/ccc-index.md +14 -15
  227. package/src/skf-setup/references/detect-and-tier.md +10 -8
  228. package/src/skf-setup/references/report.md +26 -32
  229. package/src/skf-setup/references/tier-rules.md +2 -32
  230. package/src/skf-setup/references/write-config.md +9 -10
  231. package/src/skf-test-skill/SKILL.md +10 -8
  232. package/src/skf-test-skill/customize.toml +2 -0
  233. package/src/skf-test-skill/references/coherence-check.md +19 -31
  234. package/src/skf-test-skill/references/coverage-check.md +111 -67
  235. package/src/skf-test-skill/references/detect-mode.md +10 -41
  236. package/src/skf-test-skill/references/external-validators.md +11 -19
  237. package/src/skf-test-skill/references/init.md +39 -21
  238. package/src/skf-test-skill/references/migration-section-rules.md +4 -4
  239. package/src/skf-test-skill/references/report.md +60 -40
  240. package/src/skf-test-skill/references/score.md +32 -64
  241. package/src/skf-test-skill/references/scoring-rules.md +6 -62
  242. package/src/skf-test-skill/references/source-access-protocol.md +15 -5
  243. package/src/skf-test-skill/references/step-hard-gate.md +1 -3
  244. package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
  245. package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
  246. package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
  247. package/src/skf-test-skill/scripts/compute-score.py +135 -4
  248. package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
  249. package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
  250. package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
  251. package/src/skf-update-skill/SKILL.md +17 -5
  252. package/src/skf-update-skill/customize.toml +11 -6
  253. package/src/skf-update-skill/references/detect-changes.md +48 -36
  254. package/src/skf-update-skill/references/health-check.md +5 -11
  255. package/src/skf-update-skill/references/init.md +40 -32
  256. package/src/skf-update-skill/references/manual-section-rules.md +1 -2
  257. package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
  258. package/src/skf-update-skill/references/merge.md +13 -57
  259. package/src/skf-update-skill/references/re-extract.md +27 -25
  260. package/src/skf-update-skill/references/report.md +14 -7
  261. package/src/skf-update-skill/references/validate.md +32 -30
  262. package/src/skf-update-skill/references/write.md +86 -51
  263. package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
  264. package/src/skf-verify-stack/SKILL.md +11 -21
  265. package/src/skf-verify-stack/customize.toml +12 -3
  266. package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
  267. package/src/skf-verify-stack/references/coverage.md +21 -10
  268. package/src/skf-verify-stack/references/exit-codes.md +14 -0
  269. package/src/skf-verify-stack/references/health-check.md +1 -1
  270. package/src/skf-verify-stack/references/init.md +20 -25
  271. package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
  272. package/src/skf-verify-stack/references/integrations.md +44 -49
  273. package/src/skf-verify-stack/references/report.md +28 -34
  274. package/src/skf-verify-stack/references/requirements.md +3 -3
  275. package/src/skf-verify-stack/references/synthesize.md +36 -39
  276. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
  277. package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
  278. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
  279. package/tools/cli/lib/ui.js +3 -2
  280. package/docs/deepwiki.md +0 -89
  281. package/src/skf-rename-skill/references/rebuild-context.md +0 -110
@@ -1,8 +1,11 @@
1
1
  ---
2
2
  nextStepFile: 'coherence-check.md'
3
3
  outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
4
- scoringRulesFile: 'references/scoring-rules.md'
4
+ scoringRulesFile: '{scoringRulesPath}'
5
5
  sourceAccessProtocol: 'references/source-access-protocol.md'
6
+ reconcileScript: 'scripts/reconcile-coverage.py'
7
+ coherenceScript: 'scripts/check-metadata-coherence.py'
8
+ numeratorVerifyScript: 'scripts/verify-declared-numerator.py'
6
9
  ---
7
10
 
8
11
  <!-- Config: communicate in {communication_language}. -->
@@ -35,10 +38,10 @@ Load `{sourceAccessProtocol}` and follow both sections:
35
38
 
36
39
  <!-- Subagent delegation: read SKILL.md + references/*.md, return compact JSON inventory -->
37
40
 
38
- Delegate reading of the skill under test to a subagent. The subagent receives the path to SKILL.md (and the `references/` directory path if it exists) and MUST:
41
+ Delegate reading of the skill under test to a subagent. The subagent receives the path to SKILL.md (and the `references/` directory path if it exists) and must:
39
42
  1. Read SKILL.md
40
43
  2. If a `references/` directory exists alongside SKILL.md and SKILL.md's `## Full` headings are absent or stubs, also read all `references/*.md` files
41
- 3. ONLY return this compact JSON inventory — no prose, no extra commentary:
44
+ 3. only return this compact JSON inventory — no prose, no extra commentary:
42
45
 
43
46
  ```json
44
47
  {
@@ -65,22 +68,32 @@ Delegate reading of the skill under test to a subagent. The subagent receives th
65
68
 
66
69
  **Parent uses this JSON summary as the documented inventory.** Do not load SKILL.md or references file contents into parent context.
67
70
 
68
- #### 1a. Parent-Side Schema Validation + Spot-Check (MANDATORY)
71
+ **If subagent delegation is unavailable:** the parent performs the read itself in the main thread — read SKILL.md (and, per step 2 above, all `references/*.md` when a `references/` directory exists and SKILL.md's `## Full` headings are absent or stubs) and assemble the same compact JSON inventory. The §1a schema validation and ground-truth spot-check still run on the parent-built inventory — a quality gate does not skip its own hallucination guards just because the extraction ran in-thread. This mirrors the §2 fallback ("perform ast-grep analysis in main thread") so §1 degrades gracefully instead of stalling.
69
72
 
70
- test-skill is a quality gate — it MUST NOT trust subagent output blindly. Before any downstream step consumes the inventory, the parent performs two checks and HALTs on any failure:
73
+ #### 1a. Parent-Side Schema Validation + Spot-Check
71
74
 
72
- **Schema validation (required keys + types):**
75
+ test-skill is a quality gate — it must not trust subagent output blindly. Before any downstream step consumes the inventory, the parent runs a schema validator and a grep spot-check, and HALTs on any failure.
73
76
 
74
- 1. Strip wrapping markdown fences before parsing. Subagents frequently return JSON wrapped in a code fence a line of three backticks (optionally followed by a language tag like `json`) preceding the JSON and a closing line of three backticks after it despite prompt instructions to return raw JSON. When the first non-empty line of the response is three backticks (optionally with a language tag) and the last non-empty line is three backticks, remove those two fence lines before parsing. Then parse the remaining content as JSON. On parse failure of the inner content → HALT "coverage-check: subagent response not valid JSON".
75
- 2. Required keys present: `exports` (list), `cross_check_mismatches` (list — may be empty). Missing key or wrong type → HALT "coverage-check: subagent JSON schema invalid — missing/typo: {key}". Note: the parent already knows the skill name from workflow context (`{resolved_skill_package}` from step 1) — the subagent is not required to echo it back, and doing so introduces a contract-drift surface without improving verification.
76
- 3. Each `exports[]` entry must be a dict with at minimum `name` (non-empty string) and `kind` (one of `function|class|type|constant|hook|interface|method|struct|enum|trait|macro|adapter`). The enum spans the constructs SKF actually documents across languages and skill types: JS/TS (`function`/`class`/`type`/`constant`/`hook`/`interface`/`method`), Rust public-API items (`struct`/`enum`/`trait`/`macro` — alongside the shared `type`/`constant`/`function`), and stack-composition scaffolds (`adapter`). Reject entries violating this; if >0 rejections, HALT "coverage-check: subagent returned malformed export entries — {count} entries do not match schema".
77
- 4. `cross_check_mismatches[]` entries (when non-empty) must carry `export`, `skill_md_line`, `reference_file`, `reference_line`, `issue`. Missing fields → HALT.
77
+ **Schema validation (required keys + types)delegated to `scripts/validate-inventory.py`.** Fence-stripping, JSON parsing, and the required-keys / per-entry-type / `kind`-enum / mismatch-field contract are structural validation with one correct verdict per input, so they run in the script, not in-prompt. Pipe the subagent's **raw** response (fence and all) to it exactly as §2c pipes the reconcile input:
78
78
 
79
- **Spot-check (ground-truth verification, zero-hallucination guard):**
79
+ ```bash
80
+ echo '<subagent raw response>' | uv run scripts/validate-inventory.py --stdin
81
+ ```
82
+
83
+ The script strips a wrapping markdown fence (a leading line of three backticks with an optional language tag like `json`, and a trailing line of three backticks — subagents frequently return fenced JSON despite instructions), parses the inner content, and enforces exactly this contract, returning a `violations[]` entry for each breach:
84
+
85
+ - Parses as JSON after fence stripping (parse failure → `not valid JSON`).
86
+ - Required keys present with correct types: `exports` (list), `cross_check_mismatches` (list — may be empty). Note: the parent already knows the skill name from workflow context (`{resolved_skill_package}` from step 1) — the subagent is not required to echo it back, and doing so introduces a contract-drift surface without improving verification.
87
+ - Each `exports[]` entry is a dict with at minimum `name` (non-empty string) and `kind` (one of `function|class|type|constant|hook|interface|method|struct|enum|trait|macro|adapter`). The enum spans the constructs SKF actually documents across languages and skill types: JS/TS (`function`/`class`/`type`/`constant`/`hook`/`interface`/`method`), Rust public-API items (`struct`/`enum`/`trait`/`macro` — alongside the shared `type`/`constant`/`function`), and stack-composition scaffolds (`adapter`). Malformed entries are counted in `rejectedCount`.
88
+ - Each non-empty `cross_check_mismatches[]` entry carries `export`, `skill_md_line`, `reference_file`, `reference_line`, `issue`.
89
+
90
+ The script returns `{"valid": bool, "violations": [...], "rejectedCount": N, "exportsCount": N, "inventory": {...}|null}`. **If `valid` is false → HALT** `coverage-check: subagent inventory failed schema validation — {violations joined}` (do not downgrade to a warning; a grader must not trust malformed subagent output). When `valid` is true, consume the script's returned `inventory` object as the documented inventory for the spot-check and all downstream steps — do **not** re-parse the raw response by hand.
91
+
92
+ **Spot-check (ground-truth verification, zero-hallucination guard):** operate on the validated `inventory` from the script.
80
93
 
81
- 1. If `len(exports) == 0`: skip the spot-check (no names to verify). Zero-exports policy is handled in the §2b zero-exports guard.
82
- 2. Otherwise, sample `min(3, len(exports))` exports deterministically — by default take indices `[0, len//2, len-1]` (first, middle, last) from the `exports` array after a stable sort by `name`.
83
- 3. For each sampled export, grep for the name across SKILL.md **and every reference file the subagent listed in its `references[]` array** (the documented surface of a split-body skill spans both): `grep -n "{export.name}" {resolved_skill_package}/SKILL.md {resolved_skill_package}/{each references[] path}` in the parent context. The name MUST appear at least once somewhere in that file set. Greping SKILL.md alone would false-HALT a split-body skill whose sampled export is documented only in a `references/*.md` file (a legitimate placement per §1 step 2 and the split-body note below).
94
+ 1. If `inventory.exports` is empty (`exportsCount == 0`): skip the spot-check (no names to verify). Zero-exports policy is handled in the §2b zero-exports guard.
95
+ 2. Otherwise, sample `min(3, exportsCount)` exports deterministically — by default take indices `[0, len//2, len-1]` (first, middle, last) from `inventory.exports` after a stable sort by `name`.
96
+ 3. For each sampled export, grep for the name across SKILL.md **and every reference file the subagent listed in `inventory.references`** (the documented surface of a split-body skill spans both): `grep -n "{export.name}" {resolved_skill_package}/SKILL.md {resolved_skill_package}/{each references[] path}` in the parent context. The name must appear at least once somewhere in that file set. Greping SKILL.md alone would false-HALT a split-body skill whose sampled export is documented only in a `references/*.md` file (a legitimate placement per §1 step 2 and the split-body note below).
84
97
  4. If a sampled name returns zero matches across SKILL.md **and** all listed reference files, HALT "coverage-check: subagent inventory failed ground-truth spot-check — `{name}` claimed as export but absent from SKILL.md and the listed reference files".
85
98
 
86
99
  These checks catch two hallucination classes: schema-shape drift (subagent paraphrased or dropped the contract) and fabricated exports (subagent invented names not in the document). Both are disqualifying for a grader skill — do not downgrade to a warning.
@@ -91,7 +104,7 @@ These checks catch two hallucination classes: schema-shape drift (subagent parap
91
104
 
92
105
  **Only execute if the subagent's `references` array is non-empty** (detected during split-body traversal in Section 1). Skip silently otherwise.
93
106
 
94
- The subagent has already read both SKILL.md body and `references/*.md` files. For each function, class, type, or interface that appears in BOTH the SKILL.md body AND any `references/*.md` file, instruct the subagent (or perform in the same subagent call from Section 1) to compare the documented signatures and include mismatches in its JSON output as a `cross_check_mismatches` array:
107
+ The subagent has already read both SKILL.md body and `references/*.md` files. For each function, class, type, or interface that appears in both the SKILL.md body AND any `references/*.md` file, instruct the subagent (or perform in the same subagent call from Section 1) to compare the documented signatures and include mismatches in its JSON output as a `cross_check_mismatches` array:
95
108
 
96
109
  - **Parameters:** name, type, order, optionality
97
110
  - **Return types:** exact type match
@@ -136,7 +149,7 @@ Start from the package entry point (see 0b) and identify the public API surface.
136
149
  For EACH source file that defines public API exports, delegate to a subagent that:
137
150
  1. Uses ast-grep to extract all exported symbols with their full signatures (the `source_sig`)
138
151
  2. Matches each export against the `documented_signatures` map supplied by the parent, comparing params (name, type, order, optionality) and return type
139
- 3. Returns ONLY the JSON object below — no prose, no commentary, no markdown fences:
152
+ 3. Returns only the JSON object below — no prose, no commentary, no markdown fences:
140
153
 
141
154
  ```json
142
155
  {
@@ -167,7 +180,7 @@ Parent strips wrapping markdown fences (if present) before parsing, same as §1a
167
180
 
168
181
  After the source-code analysis (§2) completes, compute `total_exports` — the count of exports discovered in the source / provenance-map / metadata.json, per the stratified-scope and State 2 rules resolved in §4.
169
182
 
170
- **Stack-skill branch (`metadata.json.skill_type == "stack"`):** A stack skill's own barrel is empty by design — it composes constituent skills rather than exporting a proprietary surface — so `total_exports` derived from its own barrel is `0` for a *correctly* built stack, and its `[from skill: …]` citations never trip §0's `[EXT:…]`-only docs-only trigger. The zero-exports HALT below targets individual source-based skills and must NOT fire for stacks. Derive the stack's coverage denominator (`stack_denominator`) from its composition surface, in priority order, and use it as `total_exports` for the rest of coverage scoring:
183
+ **Stack-skill branch (`metadata.json.skill_type == "stack"`):** A stack skill's own barrel is empty by design — it composes constituent skills rather than exporting a proprietary surface — so `total_exports` derived from its own barrel is `0` for a *correctly* built stack, and its `[from skill: …]` citations never trip §0's `[EXT:…]`-only docs-only trigger. The zero-exports HALT below targets individual source-based skills and must not fire for stacks. Derive the stack's coverage denominator (`stack_denominator`) from its composition surface, in priority order, and use it as `total_exports` for the rest of coverage scoring:
171
184
 
172
185
  1. Provenance-map cited-contract count — when `{forge_data_folder}/{skill_name}/provenance-map.json` exists **and its `entries[]` is non-empty**: count the named cited contracts, **excluding entries whose `export_name` contains `::`** (impl-block methods roll up under an already-counted type). Use the same exclusion as §4b's named-export rule so the §2b and §4b stack denominators agree.
173
186
  2. Otherwise the composition surface from `metadata.json`: `len(libraries) + len(integration_pairs)`.
@@ -197,21 +210,45 @@ Do not write the Coverage Analysis section. Do not proceed to scoring. This is a
197
210
 
198
211
  ### 2c. Reconcile Documented vs Source Surface (Deterministic Intersection)
199
212
 
200
- On a split-body skill the §1 inventory (documented surface) and the §2 AST output (source barrel) are two independent lists, so the `Documented` count must be their **intersection**, not a parent estimate. Compute three sets deterministically so the Export Coverage numerator is reproducible across runs and reviewers:
213
+ On a split-body skill the §1 inventory (documented surface) and the §2 AST output (source barrel) are two independent lists, so the `Documented` count must be their **intersection**, not a parent estimate. That reconciliation set intersection/difference/cardinality plus the grep-verified numerator of the scalar/stack branches — is deterministic arithmetic with one correct answer per input, so it is performed by `uv run {reconcileScript}`, not by hand.
214
+
215
+ **Which branch applies — and therefore which `denominatorSource` the script runs — is the policy decision made here.** The denominator itself is resolved by §4/§4b; the script consumes the *already-resolved* denominator and does only the counting (`documented_set` is derived inside the script from the §1 `exports[]`, de-duplicated and with `kind: "method"` excluded — methods are members of an already-counted class/type, not top-level barrel exports). Pick exactly one branch:
216
+
217
+ 1. **Enumerated path (`denominatorSource: "barrel"`)** — the common split-body case. `barrel_set` is the union of `exports_found[]` across every §2 per-file result. **When a stratified-scope or State-2 denominator applies (see §4) and it resolves to an enumerated name set** — the priority-2/3 re-derivation from `scope.tier_a_include` / `scope.include` globs — pass that resolved name set as `barrelSet` so the script intersects against it instead of the raw per-file union. The script computes `Documented := |documented_set ∩ barrel_set|`, `Missing := barrel_set − documented_set` (in source, not documented), `Stale := documented_set − barrel_set` (documented, not in source), and `Export Coverage = |Documented| / |barrel_set| * 100`.
218
+
219
+ 2. **Scalar-denominator branch (`denominatorSource: "scalar"`)** — §4 priority 1, when the resolved denominator is the scalar `metadata.json.stats.effective_denominator` (a count with **no enumerated name set**). There is no `barrel_set` to intersect, so the script instead greps each `documented_set` name across `SKILL.md ∪ references/*.md` and counts appearances. Pass `denominatorValue: effective_denominator` and `skillPackagePath: {resolved_skill_package}`; the script sets `Missing := effective_denominator − Documented` and returns an empty `Stale` (not enumerable without a barrel name set). If §4b's numerator-ground-truth arm fires (it triggers only when `exports_documented == effective_denominator`), its verified count is authoritative and **overrides** this numerator — do not apply both.
220
+
221
+ 3. **Stack-skill branch (`denominatorSource: "stack"`, `metadata.json.skill_type == "stack"`)** — a stack's source barrel is empty by design, so intersecting against it would divide by zero. Pass `denominatorValue: stack_denominator` (the §2b composition-surface denominator) and `compositionNames`: the provenance-map cited-contract names (`::`-excluded) when the map exists and is non-empty, else the `libraries` and `integration_pairs` names. The script greps each composition name across `SKILL.md ∪ references/*.md` for the numerator, sets `Missing := stack_denominator − Documented`, and omits `Stale` (no source barrel to enumerate against).
201
222
 
202
- **Stack-skill branch (`metadata.json.skill_type == "stack"`):** A stack's source barrel is empty by design, so `barrel_set` is `{}` and the `|documented_set barrel_set|` / `|barrel_set|` formulas in steps 3–4 would divide by zero. Do NOT use the source barrel for a stack. Instead use the §2b `stack_denominator` as the denominator and compute the numerator by full-grep verification (same mechanism as the scalar-denominator branch below): enumerate the stack's composition-surface names — the provenance-map cited-contract names (`::`-excluded) when the map exists and is non-empty, else the `libraries` and `integration_pairs` names — and for each, grep across `SKILL.md ∪ references/*.md`; `Documented` := the count that appear at least once. Set `Missing` := `stack_denominator − Documented` and omit `Stale` (no source barrel to enumerate against). Carry these into §3/§4 with `Export Coverage = Documented / stack_denominator * 100`, and skip steps 1–4 below.
223
+ **Build the reconciliation input and run the script:**
224
+
225
+ ```bash
226
+ echo '<JSON>' | uv run {reconcileScript} --stdin
227
+ ```
228
+
229
+ Input JSON (one object — supply only the fields the chosen branch needs; `{reconcileScript}` is resolved relative to the skill root):
230
+
231
+ ```json
232
+ {
233
+ "denominatorSource": "barrel | scalar | stack",
234
+ "exports": [ /* §1 inventory exports[] — used for barrel + scalar; kind:"method" entries are excluded automatically */ ],
235
+ "barrelSet": [ /* barrel: resolved enumerated name set, when §4 supplies one */ ],
236
+ "perFileResults": [ /* barrel: the §2 per-file results, unioned into barrel_set when no barrelSet */ ],
237
+ "denominatorValue": 0,
238
+ "compositionNames": [ /* stack: composition-surface names to grep */ ],
239
+ "skillPackagePath": "{resolved_skill_package}"
240
+ }
241
+ ```
203
242
 
204
- 1. **`documented_set`** := the de-duplicated set of `name` from the §1 inventory `exports[]`, excluding `kind: "method"` (methods are members of an already-counted class/type, not top-level barrel exports).
205
- 2. **`barrel_set`** := the union of `exports_found[]` across every §2 per-file result (the actual source public surface). When a stratified-scope or State-2 denominator applies (see §4) **and it resolves to an enumerated name set** — the priority-2/3 re-derivation from `scope.tier_a_include` / `scope.include` globs — `barrel_set` is that resolved denominator's name set instead of the raw union.
243
+ The script returns (read these **do not re-derive them by hand**):
206
244
 
207
- **Scalar-denominator branch (§4 priority 1):** when the resolved denominator is the scalar `metadata.json.stats.effective_denominator` (a count with **no enumerated name set**), there is no `barrel_set` to intersect against — the `documented_set ∩ barrel_set` formula in step 3 does not apply. Instead compute the numerator by full-grep verification: for each name in `documented_set`, grep it across `SKILL.md ∪ references/*.md`; `Documented` := the count of `documented_set` names that appear at least once. Use `effective_denominator` directly as the denominator, set `Missing` := `effective_denominator − Documented`, and omit `Stale` (not enumerable without a barrel name set). If §4b's numerator-ground-truth arm fires (it triggers only when `exports_documented == effective_denominator`), its verified count is authoritative and **overrides** this numerator — do not apply both.
208
- 3. Compute (**enumerated path only** skip when the scalar-denominator branch above applies):
209
- - `Documented` := `|documented_set barrel_set|`
210
- - `Missing` := `barrel_set documented_set` (in source, not documented)
211
- - `Stale` := `documented_set barrel_set` (documented, not in source)
212
- 4. Carry the resulting counts into §3's table and §4's Export Coverage: `Export Coverage = |Documented| / |barrel_set| * 100` (enumerated path) or `Documented / effective_denominator * 100` (scalar-denominator branch). Record the counts in the Coverage Analysis section so the numerator is auditable.
245
+ - `documented` — the numerator (`|documented_set ∩ barrel_set|` for barrel; grep-verified count for scalar/stack)
246
+ - `missing` / `missingCount`source names not documented (barrel enumerates the names; scalar/stack give only the residual count)
247
+ - `stale` / `staleCount` documented names not in source (barrel only; empty with `staleApplicable: false` for scalar/stack)
248
+ - `denominator` `|barrel_set|` (barrel) or the resolved scalar/stack denominator
249
+ - `exportCoverage` `documented / denominator * 100`, already rounded
213
250
 
214
- This removes the parent-side guess that otherwise swings the documented count between runs (e.g., "85 from the AST agent" vs "~120 from a hand intersection") and can cross the PASS threshold on split-body skills.
251
+ Carry these into §3's table/summary and §4's Export Coverage, and record the counts in the Coverage Analysis section (§5) so the numerator is auditable. The `exportCoverage` recorded here is the value step 5 feeds to `compute-score.py` it is the script's value, not a parent estimate.
215
252
 
216
253
  ### 3. Build Coverage Results
217
254
 
@@ -223,29 +260,22 @@ Aggregate findings across all source files:
223
260
  |--------|------|-----------|-----------------|-----------|--------|
224
261
  | {name} | function/class/type | yes/no | yes/no/unverified | src/file.ts:42 | PASS/FAIL/WARN |
225
262
 
226
- **Summary counts** (from the §2c reconciliation — not re-estimated here):
227
- - Total exports in source: `|barrel_set|`
228
- - Documented in SKILL.md: `Documented` (`|documented_set ∩ barrel_set|`)
229
- - Missing documentation: `|Missing|`
263
+ **Summary counts** (read from the §2c reconciliation script's JSON — not re-estimated here):
264
+ - Total exports in source: `denominator`
265
+ - Documented in SKILL.md: `documented`
266
+ - Missing documentation: `missingCount`
230
267
  - Signature mismatches: {N}
231
- - Undocumented in SKILL.md but not in source (stale docs): `|Stale|`
268
+ - Undocumented in SKILL.md but not in source (stale docs): `staleCount`
232
269
 
233
270
  ### 4. Load Scoring Rules
234
271
 
235
272
  Load `{scoringRulesFile}` to determine category scores:
236
273
 
237
- - **Export Coverage:** (documented / total_exports) * 100
274
+ - **Export Coverage:** the `exportCoverage` value returned by the §2c reconciliation script (`documented / denominator * 100`) — read it from the script's JSON, do not re-compute it here
238
275
  - **Signature Accuracy:** (matching_signatures / total_documented) * 100 (Forge/Deep only, "N/A" for Quick)
239
276
  - **Type Coverage:** (documented_types / total_types) * 100 (Forge/Deep only, "N/A" for Quick)
240
277
 
241
- **Stratified-scope denominator (monorepo curated subsets):** Before computing Export Coverage, check whether the Source Access Protocol's stratified-scope clause applies to this skill (see `{sourceAccessProtocol}` §Source API Surface Definition "Stratified-scope monorepo packages"). When it applies:
242
-
243
- 1. **Prefer `metadata.json.stats.effective_denominator`** when present. Use it directly as `total_exports` — but apply the **denominator deflation guard** from `{sourceAccessProtocol}` stratified-scope resolution step 1: when source is readable, re-derive the source barrel and, if it exceeds `effective_denominator` by >25% with no `scope.tier_a_include`, treat the stored value as deflated, use the re-derived count, and emit the `denominator deflation` gap.
244
- 2. **Otherwise re-derive at test time** from the brief's scope globs per the protocol. When the brief supplies `scope.tier_a_include`, re-derive from that narrower list (excluding umbrella barrel files per the protocol's umbrella-barrel note); otherwise re-derive from `scope.include`. Use the resulting union count as `total_exports`.
245
- 3. **Run the denominator inflation check** defined in `{sourceAccessProtocol}` stratified-scope resolution step 3 whenever re-derivation fell back to `scope.include`. If the `scope.include` union exceeds the provenance-map entry count by more than 25%, emit the Medium-severity `denominator inflation — coarse scope.include union exceeds authored surface` gap and append it to the Coverage Analysis gap list.
246
- 4. **Apply provenance-map canonicalization** before intersecting documented exports against the raw provenance-map entry list — see `{sourceAccessProtocol}` §Source API Surface Definition → "Provenance-map canonicalization" for the folding rules (`_def`/`_exact` suffix, `a11y_` prefix, renderer-prefix disambiguation). Skip folding when `metadata.json.stats.effective_denominator` is present and already equals the raw provenance-map entry count. Record the fold summary in the Coverage Analysis section so it's auditable.
247
-
248
- Record the denominator source in the Coverage Analysis section as `Denominator: stratified ({effective_denominator | tier_a_include union | scope.include union}, {N} files matched)`. When stratified scope does not apply, use the standard barrel-based denominator and omit the stratified annotation.
278
+ **Resolve the coverage denominator per `{sourceAccessProtocol}` (already loaded in §0b) do not re-derive its ladders here.** Determine which §Source API Surface Definition clause matches this skill and apply that clause exactly as written: **stratified-scope** (monorepo curated subset), **multi-entry (exports-map)**, **specific-modules**, **pattern-reference**, or the **State 2** provenance-vs-metadata cross-reference (union on divergence). Each clause fixes its own resolution priority (prefer `metadata.json.stats.effective_denominator` `scope.tier_a_include` `scope.include` / subpath union), its deflation and inflation guards, the umbrella-barrel exclusion, and provenance-map canonicalization (including the fold summary the canonicalization records). Use the clause's resolved value as `total_exports`; when no clause matches, use the standard barrel-based denominator. Record the denominator source in the Coverage Analysis section using the exact `Denominator: {barrel | stratified (…) | multi-entry (…) | specific-modules (…) | pattern-reference (…)}` annotation string the matching clause specifies.
249
279
 
250
280
  **Record the two non-chosen candidate values alongside the chosen one.**
251
281
  Stratified-scope resolution picks ONE of three denominator candidates
@@ -260,13 +290,15 @@ as-observed (or `absent` when the candidate was not present for this skill):
260
290
  - `stats.effective_denominator`: {N | absent} {← chosen if priority (1) applied}
261
291
  - `scope.tier_a_include` union: {N | absent} {← chosen if priority (2) applied}
262
292
  - `scope.include` union: {N | absent} {← chosen if priority (3) applied}
293
+ - exports-map subpath union: {N | absent} {← chosen if the multi-entry clause applied}
294
+ - root barrel: {N} {secondary candidate — root-barrel-vs-subpath-union audit}
263
295
  ```
264
296
 
265
297
  Readers can then spot-check whether the chosen denominator is reasonable
266
298
  against the other two without re-running the extraction. A future reviewer who
267
- suspects denominator gaming has the evidence inline.
268
-
269
- **State 2 denominator validation:** When using provenance-map as the baseline (State 2), cross-reference the provenance-map entry count against `metadata.json`'s `exports[]` array before computing Export Coverage. If they diverge, use the union as the denominator per the source-access-protocol rules. Log the gap size if any. The stratified-scope rule above takes precedence when both conditions apply — compute the stratified denominator first, then validate the provenance-map entry count against it.
299
+ suspects denominator gaming has the evidence inline. The `multi-entry` clause
300
+ requires the root-barrel named-export count in the `root barrel` row so the
301
+ root-barrel-vs-subpath-union choice is auditable.
270
302
 
271
303
  ### 4b. Metadata Export-Count Coherence Cross-Check
272
304
 
@@ -286,27 +318,49 @@ After the denominator has been resolved (standard, stratified, or State 2), cros
286
318
  **Cluster B — documented surface** (what was extracted and documented, including methods and submodule members):
287
319
 
288
320
  3. `metadata.json.stats.exports_documented` — the declared documented count
289
- 4. Provenance-map **named-export count** (if `{forge_data_folder}/{skill_name}/provenance-map.json` exists) — count only top-level named exports: **exclude entries whose `export_name` contains `::`** (impl-block methods like `Type::method`, which roll up under an already-counted type and are not separate barrel exports). Comparing the raw entry count instead false-positives on any method-enumerating provenance map (common for Rust/TS type-heavy skills): e.g. a map with 88 named exports + 48 `Type::method` entries reports 136 against `exports_documented` ≈ 92 → a spurious ~32% Cluster-B drift, while the comparable named-export count (88) agrees within ~4%.
290
- 5. `confidence_distribution` sum (`t1 + t1_low + t2 + t3`, when present in `metadata.json.stats`) — every extracted/documented export is binned into exactly one confidence tier, so the distribution must sum to the documented-surface total; a divergence (e.g., distribution sums to 91 while `exports_documented` is 85) is an internal-consistency defect even when the two clusters look fine
321
+ 4. Provenance-map **named-export count** (if `{forge_data_folder}/{skill_name}/provenance-map.json` exists) — pass the raw `export_name` values as `provenanceExportNames`; the script counts top-level named exports, **excluding entries whose `export_name` contains `::`** (impl-block methods like `Type::method`, which roll up under an already-counted type and are not separate barrel exports). Comparing the raw entry count instead false-positives on any method-enumerating provenance map (common for Rust/TS type-heavy skills): e.g. a map with 88 named exports + 48 `Type::method` entries reports 136 against `exports_documented` ≈ 92 → a spurious ~32% Cluster-B drift, while the comparable named-export count (88) agrees within ~4%.
322
+ 5. `confidence_distribution` (`t1`, `t1_low`, `t2`, `t3`, when present in `metadata.json.stats`) — pass the tiers as `confidenceDistribution`; the script sums them. Every extracted/documented export is binned into exactly one confidence tier, so the distribution must sum to the documented-surface total; a divergence (e.g., distribution sums to 91 while `exports_documented` is 85) is an internal-consistency defect even when the two clusters look fine
291
323
 
292
324
  Cluster assignment is canonical: `skf-create-skill` step 5 derives `exports_public_api` from entry-point validation and writes the `exports[]` array from the same barrel surface (see `skf-create-skill/references/compile.md:105`), while `exports_documented` tracks the broader documented surface that the provenance-map also enumerates.
293
325
 
294
- **Intra-cluster divergence (Medium):** For each cluster, if two or more counts are present and the largest and smallest disagree by more than 10% of the larger, emit a **Medium**-severity gap titled `metadata drift{cluster} export counts diverge` (substitute `barrel` for Cluster A, `documented-surface` for Cluster B). Enumerate the offending counts in the gap body (e.g., `stats.exports_public_api=55, exports[].length=48` 13% drift). This is the real drift signal the two sources should mirror the same surface and they don't, so upstream extraction or compilation produced inconsistent output that a re-compile should reconcile. Classify under structural/metadata coherence regardless of naive/contextual mode.
326
+ **Delegate the drift arithmetic to `uv run {coherenceScript}`.** The intra-cluster / cross-cluster `>10%` comparisons are deterministic count arithmetic with one correct answer per input, so the script not the promptowns the binning, the divergence percentages, and every skip condition (a cluster with fewer than two present counts, and clusters that agree within the threshold, are skipped inside the script). Build the input from the counts collected above and run it (`{coherenceScript}` resolves relative to the skill root):
295
327
 
296
- **Cross-cluster divergence (Info):** After intra-cluster checks, if both clusters resolved to a representative count (pick the higher of each cluster's available counts) and the two cluster values differ by more than 10%, append a single **Info**-severity note titled `multi-denominator reporting — barrel vs documented surface` with both values (e.g., `barrel=55, documented=114`). This is expected for skills whose documented surface intentionally exceeds the barrel (methods, submodule members, re-exported classes) — it is not drift. The note exists so the test report makes the dual-denominator design visible and auditable without demanding action.
328
+ ```bash
329
+ echo '<JSON>' | uv run {coherenceScript} --stdin
330
+ ```
297
331
 
298
- **When a cluster has only one count available:** Skip that cluster's intra-cluster check silently — there is nothing to cross-check within it.
332
+ Input JSON (omit any count that is absent; `driftThresholdPct` defaults to `10`):
299
333
 
300
- **When both clusters agree within 10% of each other:** Skip the cross-cluster note silently — no multi-denominator reporting is in play.
334
+ ```json
335
+ {
336
+ "skillType": "{metadata.json.skill_type or null}",
337
+ "scopeType": "{metadata.json.scope_type or null}",
338
+ "clusterA": {"exports_public_api": 0, "exports_length": 0},
339
+ "clusterB": {"exports_documented": 0},
340
+ "provenanceExportNames": [ /* raw provenance-map export_name values — the script excludes `::` impl-block methods and counts the rest */ ],
341
+ "confidenceDistribution": {"t1": 0, "t1_low": 0, "t2": 0, "t3": 0}
342
+ }
343
+ ```
301
344
 
302
- **When only one count is available across both clusters:** Skip silentlythere is nothing to cross-check.
345
+ The script returns `{"skipped": bool, "clusterACounts": {...}, "clusterBCounts": {...}, "findings": [...]}`. Read `findings[]` and append each entry directly **do not re-derive the percentages by hand.** Each entry carries `severity`, `title`, `detail` (the enumerated counts + drift %), and `category: structural/metadata coherence`:
303
346
 
304
- **Numerator ground-truth force a full grep on the inflation signature:** The intra/cross-cluster checks above only compare *counts*; they cannot tell whether the declared documented exports actually appear in the skill. When `metadata.json.stats.exports_documented == effective_denominator` exactly (the numerator equals the denominator — the signature of a numerator inflated to match the full surface), do **not** trust the documented count. Grep every declared export name (the full `metadata.exports[]` / provenance-map declared set not the §1a 3-sample) against `SKILL.md ∪ references/*.md`. The count of declared names that actually appear is the **verified numerator**:
347
+ - A **Medium** `metadata drift {barrel|documented-surface} export counts diverge` is the real drift signal — the two sources should mirror the same surface and they don't, so upstream extraction or compilation produced inconsistent output that a re-compile should reconcile. It is classified under structural/metadata coherence regardless of naive/contextual mode.
348
+ - An **Info** `multi-denominator reporting — barrel vs documented surface` is expected for skills whose documented surface intentionally exceeds the barrel (methods, submodule members, re-exported classes) — it is not drift. The note exists so the test report makes the dual-denominator design visible and auditable without demanding action.
305
349
 
306
- - If verified == declared, the skill is genuinely fully documented no finding; coverage stands.
307
- - If verified < declared, emit a **High**-severity gap `numerator inflation — {declared − verified} of {declared} declared exports absent from SKILL.md/references` listing the absent names, and use the verified count as the Export Coverage numerator (overriding `exports_documented`). A numerator padded to equal the denominator otherwise produces a tautological 100% that passes the gate.
350
+ (The stack and reference-app branches above already skip this delegation; the script also returns `skipped: true` when passed their `skillType` / `scopeType`, so an unconditional call stays safe.)
308
351
 
309
- The full grep runs only on the exact equality signature, so it adds no cost to the common case where the numerator is already below the denominator. Unlike the count-coherence findings above, this arm is authoritative it changes the numerator used for scoring.
352
+ **Numerator ground-truth — force a full grep on the inflation signature.** The intra/cross-cluster checks above only compare *counts*; they cannot tell whether the declared exports actually appear in the skill. When `metadata.json.stats.exports_documented == effective_denominator` exactly (numerator equals denominator — the signature of a numerator padded to match the full surface), do not trust the documented count: grep the full declared set (the `metadata.exports[]` / provenance-map names, not the §1a 3-sample) against `SKILL.md references/*.md`. That grep + present/absent set-diff + count has one correct answer per input, so it runs in `uv run {numeratorVerifyScript}`, not in-prompt (`{numeratorVerifyScript}` resolves relative to the skill root):
353
+
354
+ ```bash
355
+ echo '{"declaredNames": [ /* full declared set */ ], "skillPackagePath": "{resolved_skill_package}"}' | uv run {numeratorVerifyScript} --stdin
356
+ ```
357
+
358
+ Read the script's output — do not re-derive it by hand:
359
+
360
+ - `inflated: false` (`verified == declared`) → the skill is genuinely fully documented; no finding, coverage stands.
361
+ - `inflated: true` (`verified < declared`) → emit a **High**-severity gap `numerator inflation — {declared − verified} of {declared} declared exports absent from SKILL.md/references` listing the script's `absent[]` names, and use `verified` as the Export Coverage numerator (overriding `exports_documented`). A numerator padded to equal the denominator otherwise produces a tautological 100% that passes the gate.
362
+
363
+ The grep runs only on the exact-equality signature, so it adds no cost to the common case where the numerator is already below the denominator. Unlike the count-coherence findings above, this arm is authoritative — it changes the numerator used for scoring.
310
364
 
311
365
  Append any findings (Medium gaps, the Info note, and/or the High numerator-inflation gap) to the Coverage Analysis section's gap list (built in section 5) so they surface in the final test report alongside coverage and signature findings. The count-coherence findings are informational about data quality and do not change the denominator chosen above; the numerator ground-truth arm is the one exception that overrides the numerator.
312
366
 
@@ -350,17 +404,7 @@ Note: Weight application is deferred to step 5 where all category weights are ca
350
404
 
351
405
  ### 6. Report Coverage Results
352
406
 
353
- "**Coverage check complete.**
354
-
355
- **{skill_name}** — {forge_tier} tier analysis of {file_count} source files:
356
-
357
- - Exports: {documented}/{total} documented ({percentage}%)
358
- - Signatures: {matching}/{total} accurate ({percentage}% or N/A for Quick)
359
- - Types: {documented_types}/{total_types} covered ({percentage}% or N/A for Quick)
360
-
361
- **{N} issues found** — details in Coverage Analysis section.
362
-
363
- **Proceeding to coherence check...**"
407
+ Report the coverage result to the user: the {forge_tier}-tier analysis of {file_count} source files, the documented ratios for exports / signatures / types (signatures and types are N/A for Quick tier), and the issue count — full details are in the Coverage Analysis section. Then proceed to the coherence check.
364
408
 
365
409
  Update stepsCompleted, then load and execute {nextStepFile}.
366
410
 
@@ -11,41 +11,19 @@ outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
11
11
 
12
12
  Examine the skill metadata to determine whether this is an individual skill (naive mode — API surface coverage only) or a stack skill (contextual mode — full coherence validation including cross-references and integration patterns).
13
13
 
14
- ### 1. Examine Skill Type
14
+ ### 1. Determine Test Mode
15
15
 
16
- Read the skill metadata (loaded in step 01) and check:
16
+ Read the skill metadata (loaded in step 01) and branch on its `skill_type` field — a single deterministic lookup:
17
17
 
18
- **Individual Skill indicators:**
19
- - `skill_type: 'single'` in metadata
20
- - Single source library/package
21
- - No cross-references to other skills
22
- - Self-contained API surface
18
+ - `skill_type: 'single'` → **Naive Mode** (API-surface coverage; coherence is structural only, no coherence category in scoring)
19
+ - `skill_type: 'stack'` **Contextual Mode** (full coherence validation — cross-references resolve, types match, integration patterns complete; coherence category scored)
20
+ - unset or unclear → default to **Naive Mode** (conservative — fewer checks, less chance of false negatives from missing context) and note the default in the report
23
21
 
24
- **Stack Skill indicators:**
25
- - `skill_type: 'stack'` in metadata
26
- - References multiple skills or packages
27
- - Contains cross-references in SKILL.md
28
- - Integration patterns documented between components
22
+ What each mode actually checks and how category weights are distributed is owned by `scoring-rules.md` (Tier-Dependent Scoring) and `coherence-check.md` — do not restate it here.
29
23
 
30
- ### 2. Determine Test Mode
24
+ **Quick-tier adjustment (applies to both modes):** If `forge_tier` is `Quick`, Signature Accuracy and Type Coverage are skipped during scoring (no AST available); their weights are redistributed proportionally to the remaining active categories per `scoring-rules.md` Tier-Dependent Scoring.
31
25
 
32
- **IF individual skill Naive Mode:**
33
- - Coverage check: documented exports vs source API surface
34
- - Coherence check: basic structural validation only (no cross-references to verify)
35
- - Scoring: redistributed weights (no coherence category)
36
-
37
- **IF stack skill → Contextual Mode:**
38
- - Coverage check: documented exports vs source API surface (same as naive)
39
- - Coherence check: full validation — cross-references exist, types match, integration patterns complete
40
- - Scoring: full category weights including coherence
41
-
42
- **IF metadata unclear or skill_type not set:**
43
- - Default to **naive mode** (conservative — fewer checks, less chance of false negatives from missing context)
44
- - Note the default in the report
45
-
46
- **Quick-tier adjustment (applies to both modes):** If `forge_tier` is `Quick`, Signature Accuracy and Type Coverage are skipped during scoring (no AST available). Their weights are redistributed proportionally to remaining active categories. See `scoring-rules.md` Tier-Dependent Scoring section for details.
47
-
48
- ### 3. Update Output Document
26
+ ### 2. Update Output Document
49
27
 
50
28
  Update `{outputFile}` frontmatter:
51
29
  - Set `testMode: '{naive|contextual}'`
@@ -66,18 +44,9 @@ Append the **Test Summary** section to `{outputFile}`:
66
44
  - Coherence Check: {what will be checked based on mode + tier}
67
45
  ```
68
46
 
69
- ### 4. Report Mode Detection
70
-
71
- "**Mode detected: {NAIVE|CONTEXTUAL}**
72
-
73
- **{skill_name}** is {an individual skill / a stack skill}, so we'll run in **{naive/contextual}** mode.
74
-
75
- **What this means:**
76
- - Coverage: {brief description of coverage scope}
77
- - Coherence: {brief description of coherence scope}
78
- - Scoring: {which weight distribution applies}
47
+ ### 3. Report Mode Detection
79
48
 
80
- **Proceeding to coverage check...**"
49
+ Report the detected mode ({naive|contextual}) and why it was selected (individual skill → naive, stack → contextual), then proceed to the coverage check.
81
50
 
82
51
  Update stepsCompleted, then load and execute {nextStepFile}.
83
52
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  nextStepFile: 'step-hard-gate.md'
3
3
  outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
4
+ externalScoreScript: 'scripts/combine-external-scores.py'
4
5
  ---
5
6
 
6
7
  <!-- Config: communicate in {communication_language}. -->
@@ -25,7 +26,7 @@ Before running external validators, check if `{forge_data_folder}/{skill_name}/e
25
26
  - If the command fails (exit code non-zero) or git is not available, the file is either **untracked** (new, never committed) or we're in a **non-git environment**:
26
27
  - Check if `{skillDir}/metadata.json` exists and has a `generation_date` field
27
28
  - Compare `metadata.json` `generation_date` against the evidence report's generation date (from its frontmatter `generated` field or the `## Validation Results` timestamp)
28
- - **Precision guard (mirror of the git-path Primary-cross check):** date-granularity equality is NOT proof of same-session generation. A same-day `update-skill` that regenerates SKILL.md *after* the cached evidence report was produced yields the same calendar date (e.g. `metadata.generation_date: 2026-05-23T00:00:00Z` vs evidence `generated: 2026-05-23`), so reusing on date-equality alone would publish pre-update scores for post-update content. Auto-reuse is safe **only** when BOTH timestamps carry a real time-of-day component — neither a date-only string (`2026-05-23`) nor a midnight-coerced `…T00:00:00Z` — AND they match to the minute. In that case auto-reuse: the evidence report was generated from the same SKILL.md content.
29
+ - **Precision guard (mirror of the git-path Primary-cross check):** date-granularity equality is not proof of same-session generation. A same-day `update-skill` that regenerates SKILL.md *after* the cached evidence report was produced yields the same calendar date (e.g. `metadata.generation_date: 2026-05-23T00:00:00Z` vs evidence `generated: 2026-05-23`), so reusing on date-equality alone would publish pre-update scores for post-update content. Auto-reuse is safe **only** when both timestamps carry a real time-of-day component — neither a date-only string (`2026-05-23`) nor a midnight-coerced `…T00:00:00Z` — AND they match to the minute. In that case auto-reuse: the evidence report was generated from the same SKILL.md content.
29
30
  - Otherwise — if either timestamp is date-only or midnight-coerced, if they differ, or if `metadata.json` is missing or has no `generation_date` — treat as stale and proceed to section 2 for a fresh run. Forcing a fresh run on ambiguous precision matches the git path's bias toward freshness over reusing possibly-stale scores.
30
31
  - Note: "Staleness check: SKILL.md is untracked/non-git — using metadata.json timestamp comparison (date-only/midnight timestamps force a fresh run)."
31
32
  - If the command succeeds (file is tracked by git), continue to Primary check below.
@@ -102,7 +103,7 @@ timeout 120s npx --no-install -y tessl skill review {skillDir}
102
103
 
103
104
  Timeout handling mirrors skill-check: exit `124` → `tessl_score: N/A` with reason `timeout-120s`. If the percentage regex (`/(Description|Content|Review Score):\s*(\d+)%/`) returns fewer than three matches, record `tessl_score: N/A` with reason `parse-failure` and include the first 200 chars of output in evidence-report for debugging.
104
105
 
105
- **Registry-404 branch:** if the invocation emits `npm error 404 Not Found` or the npx wrapper exits with a not-found condition, record `tessl_score: N/A` with reason `pin-not-on-registry` and continue. This branch exists because tessl has historically shipped under shifting scope/tag combinations; do not HALT the workflow on a missing registry entry.
106
+ **Registry-404 branch:** if the invocation emits `npm error 404 Not Found` or the npx wrapper exits with a not-found condition, record `tessl_score: N/A` with reason `pin-not-on-registry` and continue. tessl has historically shipped under shifting scope/tag combinations, so a missing registry entry does not HALT the workflow.
106
107
 
107
108
  **Parse the output** to extract:
108
109
  - `description_score` — percentage (e.g., 100%)
@@ -123,17 +124,18 @@ Store in context: `tessl_description_score`, `tessl_content_score`, `tessl_revie
123
124
 
124
125
  ### 4. Calculate Combined External Score
125
126
 
126
- **If both tools ran:**
127
+ The combined external score feeds `externalValidation` into the scoring script (step 5), so its mean is computed by a script, not in-prompt. Both scores are on the same 0-100 scale (skill-check quality score; tessl review percentage). Pass each score, or `null` when its tool did not run or returned N/A (`{externalScoreScript}` resolves relative to the skill root):
127
128
 
128
- ```
129
- external_score = (skill_check_score + tessl_review_score) / 2
129
+ ```bash
130
+ echo '{"skillCheckScore": <score or null>, "tesslReviewScore": <score or null>}' | uv run {externalScoreScript} --stdin
130
131
  ```
131
132
 
132
- Note: `skill_check_score` is 0-100, `tessl_review_score` is 0-100%. Both are on the same scale.
133
+ Read the result do not re-average by hand:
133
134
 
134
- **If only one tool ran:** Use that tool's score as the external score.
135
+ - `externalScore` the combined score: the mean when both tools ran, the single score when one ran, or `null` when neither ran (the scoring step redistributes the external-validation weight on `null`).
136
+ - `toolsUsed[]` — the tools that contributed.
135
137
 
136
- **If neither tool ran:** Record `external_score: N/A`. The scoring step will redistribute the external validation weight.
138
+ Record `external_score: N/A` when `externalScore` is `null`.
137
139
 
138
140
  ### 5. Append External Validation to Output
139
141
 
@@ -165,17 +167,7 @@ Append to `{outputFile}`:
165
167
 
166
168
  ### 6. Report Results
167
169
 
168
- "**External validation complete.**
169
-
170
- | Tool | Score | Status |
171
- |------|-------|--------|
172
- | skill-check | {score}/100 | {available/skipped} |
173
- | tessl | {average}% | {available/skipped} |
174
- | **Combined** | **{external_score}%** | |
175
-
176
- {content quality warning if tessl content < 70%}
177
-
178
- **Proceeding to scoring...**"
170
+ Report the external validation result to the user: the per-tool scores and availability (skill-check out of 100, tessl as a percentage average, each `available` or `skipped`), the combined external score, and a content-quality warning if tessl content is below 70%. Then proceed to scoring.
179
171
 
180
172
  Update stepsCompleted, then load and execute {nextStepFile}.
181
173