bmad-module-skill-forge 2.2.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +49 -49
  3. package/docs/404.md +10 -8
  4. package/docs/_data/pinned.yaml +22 -13
  5. package/docs/_internal/STABILITY.md +58 -35
  6. package/docs/agents.md +39 -53
  7. package/docs/architecture.md +65 -51
  8. package/docs/bmad-synergy.md +53 -51
  9. package/docs/campaign.md +127 -75
  10. package/docs/concepts.md +34 -28
  11. package/docs/examples.md +70 -68
  12. package/docs/forge-auto.md +34 -31
  13. package/docs/getting-started.md +118 -57
  14. package/docs/how-it-works.md +25 -21
  15. package/docs/index.md +15 -15
  16. package/docs/skill-model.md +70 -59
  17. package/docs/troubleshooting.md +135 -18
  18. package/docs/verifying-a-skill.md +83 -57
  19. package/docs/why-skf.md +24 -22
  20. package/docs/workflows.md +274 -110
  21. package/package.json +18 -10
  22. package/src/forger/preferences.yaml +8 -1
  23. package/src/knowledge/agentskills-spec.md +2 -0
  24. package/src/knowledge/ccc-bridge.md +40 -24
  25. package/src/knowledge/confidence-tiers.md +9 -5
  26. package/src/knowledge/overview.md +3 -3
  27. package/src/knowledge/progressive-capability.md +8 -8
  28. package/src/knowledge/provenance-tracking.md +9 -5
  29. package/src/knowledge/qmd-registry.md +2 -2
  30. package/src/knowledge/skf-knowledge-index.csv +2 -2
  31. package/src/knowledge/skill-lifecycle.md +18 -11
  32. package/src/knowledge/split-body-strategy.md +0 -3
  33. package/src/knowledge/tool-resolution.md +4 -2
  34. package/src/knowledge/version-paths.md +101 -15
  35. package/src/module-help.csv +3 -3
  36. package/src/module.yaml +10 -0
  37. package/src/shared/data/ast-grep-recipes.yaml +608 -0
  38. package/src/shared/data/ide-context-files.json +30 -0
  39. package/src/shared/health-check.md +32 -12
  40. package/src/shared/references/description-guard-protocol.md +12 -9
  41. package/src/shared/references/feasibility-report-schema.md +19 -13
  42. package/src/shared/references/headless-gate-convention.md +55 -3
  43. package/src/shared/references/output-contract-schema.md +19 -8
  44. package/src/shared/references/pipeline-contracts.md +94 -29
  45. package/src/shared/references/tessl-review.md +77 -0
  46. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
  47. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
  48. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
  49. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
  50. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
  51. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
  52. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
  53. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
  54. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
  55. package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
  56. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
  57. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
  58. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
  59. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
  60. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
  61. package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
  62. package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
  63. package/src/shared/scripts/skf-atomic-write.py +30 -14
  64. package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
  65. package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
  66. package/src/shared/scripts/skf-check-unit-records.py +184 -0
  67. package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
  68. package/src/shared/scripts/skf-classify-changed-files.py +694 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
  71. package/src/shared/scripts/skf-count-imports.py +1867 -0
  72. package/src/shared/scripts/skf-count-tokens.py +1 -1
  73. package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
  74. package/src/shared/scripts/skf-description-guard.py +99 -11
  75. package/src/shared/scripts/skf-detect-docs.py +694 -2
  76. package/src/shared/scripts/skf-detect-language.py +486 -107
  77. package/src/shared/scripts/skf-detect-registry.py +887 -0
  78. package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
  79. package/src/shared/scripts/skf-detect-tools.py +209 -27
  80. package/src/shared/scripts/skf-detect-workspaces.py +506 -30
  81. package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
  82. package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
  83. package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
  84. package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
  85. package/src/shared/scripts/skf-extract-public-api.py +4084 -85
  86. package/src/shared/scripts/skf-extraction-inventory.py +746 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
  88. package/src/shared/scripts/skf-fetch-temporal.py +674 -0
  89. package/src/shared/scripts/skf-find-cycles.py +97 -29
  90. package/src/shared/scripts/skf-find-test-report.py +476 -0
  91. package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +661 -0
  94. package/src/shared/scripts/skf-hash-content.py +166 -12
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +159 -27
  97. package/src/shared/scripts/skf-manifest-ops.py +1 -1
  98. package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
  99. package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
  100. package/src/shared/scripts/skf-names-present.py +221 -0
  101. package/src/shared/scripts/skf-pair-intersect.py +119 -28
  102. package/src/shared/scripts/skf-parse-gaps.py +1384 -0
  103. package/src/shared/scripts/skf-preapply.py +1 -1
  104. package/src/shared/scripts/skf-preflight.py +161 -27
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
  107. package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
  108. package/src/shared/scripts/skf-quick-batch.py +774 -0
  109. package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
  110. package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
  111. package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
  112. package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
  113. package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
  114. package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
  115. package/src/shared/scripts/skf-resolve-package.py +523 -106
  116. package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
  117. package/src/shared/scripts/skf-run-lock.py +577 -0
  118. package/src/shared/scripts/skf-scan-manifests.py +998 -57
  119. package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
  120. package/src/shared/scripts/skf-severity-classify.py +712 -108
  121. package/src/shared/scripts/skf-shape-detect.py +298 -204
  122. package/src/shared/scripts/skf-shard-body.py +9 -8
  123. package/src/shared/scripts/skf-skill-inventory.py +2412 -89
  124. package/src/shared/scripts/skf-skills-module.py +472 -0
  125. package/src/shared/scripts/skf-source-tree.py +1796 -0
  126. package/src/shared/scripts/skf-structural-diff.py +670 -119
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +5 -5
  129. package/src/shared/scripts/skf-update-run-state.py +624 -0
  130. package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
  131. package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
  132. package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
  133. package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
  134. package/src/shared/scripts/skf-validate-output.py +712 -64
  135. package/src/shared/scripts/skf-validate-pins.py +1 -1
  136. package/src/shared/scripts/skf-verify-no-trace.py +116 -10
  137. package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
  138. package/src/shared/scripts/skf-write-skill-brief.py +367 -19
  139. package/src/shared/tool-requirements.yaml +201 -0
  140. package/src/skf-analyze-source/SKILL.md +41 -36
  141. package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
  142. package/src/skf-analyze-source/customize.toml +36 -24
  143. package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
  144. package/src/skf-analyze-source/references/continue.md +2 -13
  145. package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
  146. package/src/skf-analyze-source/references/generate-briefs.md +121 -55
  147. package/src/skf-analyze-source/references/headless-contract.md +42 -15
  148. package/src/skf-analyze-source/references/identify-units.md +96 -110
  149. package/src/skf-analyze-source/references/init.md +73 -71
  150. package/src/skf-analyze-source/references/map-and-detect.md +123 -105
  151. package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
  152. package/src/skf-analyze-source/references/recommend.md +51 -70
  153. package/src/skf-analyze-source/references/scan-project.md +60 -84
  154. package/src/skf-analyze-source/references/scan-root.md +39 -0
  155. package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
  156. package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
  157. package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
  158. package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
  159. package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
  160. package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
  161. package/src/skf-audit-skill/SKILL.md +43 -51
  162. package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
  163. package/src/skf-audit-skill/customize.toml +41 -20
  164. package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
  165. package/src/skf-audit-skill/references/doc-drift.md +141 -0
  166. package/src/skf-audit-skill/references/headless-contract.md +29 -0
  167. package/src/skf-audit-skill/references/init.md +148 -210
  168. package/src/skf-audit-skill/references/re-index.md +96 -108
  169. package/src/skf-audit-skill/references/report.md +96 -33
  170. package/src/skf-audit-skill/references/semantic-diff.md +27 -18
  171. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  172. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  173. package/src/skf-audit-skill/references/structural-diff.md +75 -90
  174. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  175. package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
  176. package/src/skf-brief-skill/SKILL.md +36 -25
  177. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  178. package/src/skf-brief-skill/assets/scope-templates.md +17 -24
  179. package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
  180. package/src/skf-brief-skill/customize.toml +48 -20
  181. package/src/skf-brief-skill/references/analyze-target.md +142 -125
  182. package/src/skf-brief-skill/references/confirm-brief.md +10 -8
  183. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  184. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  185. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  186. package/src/skf-brief-skill/references/headless-args.md +84 -16
  187. package/src/skf-brief-skill/references/health-check.md +1 -1
  188. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  189. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  190. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  191. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  192. package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
  193. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  194. package/src/skf-brief-skill/references/version-resolution.md +3 -2
  195. package/src/skf-brief-skill/references/write-brief.md +66 -57
  196. package/src/skf-campaign/SKILL.md +31 -34
  197. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  198. package/src/skf-campaign/customize.toml +47 -16
  199. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  200. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  201. package/src/skf-campaign/references/health-check.md +7 -16
  202. package/src/skf-campaign/references/step-01-setup.md +46 -84
  203. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  204. package/src/skf-campaign/references/step-03-pins.md +26 -18
  205. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  206. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  207. package/src/skf-campaign/references/step-06-batch.md +41 -34
  208. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  209. package/src/skf-campaign/references/step-08-verify.md +28 -29
  210. package/src/skf-campaign/references/step-09-refine.md +29 -30
  211. package/src/skf-campaign/references/step-10-export.md +48 -36
  212. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  213. package/src/skf-campaign/references/step-resume.md +37 -68
  214. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  215. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  216. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  217. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  218. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  219. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  220. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  221. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  222. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  223. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  224. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  225. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  226. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  227. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  228. package/src/skf-create-skill/SKILL.md +27 -20
  229. package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
  230. package/src/skf-create-skill/assets/skill-sections.md +15 -5
  231. package/src/skf-create-skill/customize.toml +20 -8
  232. package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
  233. package/src/skf-create-skill/references/batch-mode.md +81 -0
  234. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  235. package/src/skf-create-skill/references/compile.md +107 -29
  236. package/src/skf-create-skill/references/component-extraction.md +113 -91
  237. package/src/skf-create-skill/references/enrich.md +41 -23
  238. package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
  239. package/src/skf-create-skill/references/extract.md +200 -161
  240. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  241. package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
  242. package/src/skf-create-skill/references/extraction-patterns.md +584 -196
  243. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  244. package/src/skf-create-skill/references/health-check.md +1 -1
  245. package/src/skf-create-skill/references/load-brief.md +53 -36
  246. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  247. package/src/skf-create-skill/references/report.md +28 -59
  248. package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
  249. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  250. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  251. package/src/skf-create-skill/references/step-doc-sources.md +28 -19
  252. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  253. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
  254. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  255. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  256. package/src/skf-create-skill/references/validate.md +173 -99
  257. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  258. package/src/skf-create-stack-skill/SKILL.md +29 -56
  259. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  260. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  261. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  262. package/src/skf-create-stack-skill/customize.toml +23 -12
  263. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  264. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  265. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  266. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  267. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  268. package/src/skf-create-stack-skill/references/init.md +90 -32
  269. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  270. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  271. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  272. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  273. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  274. package/src/skf-create-stack-skill/references/report.md +39 -33
  275. package/src/skf-create-stack-skill/references/validate.md +20 -39
  276. package/src/skf-drop-skill/SKILL.md +28 -51
  277. package/src/skf-drop-skill/customize.toml +44 -30
  278. package/src/skf-drop-skill/references/execute.md +79 -146
  279. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  280. package/src/skf-drop-skill/references/report.md +46 -30
  281. package/src/skf-drop-skill/references/select.md +118 -124
  282. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  283. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  284. package/src/skf-export-skill/SKILL.md +40 -62
  285. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  286. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  287. package/src/skf-export-skill/customize.toml +42 -13
  288. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  289. package/src/skf-export-skill/references/health-check.md +2 -0
  290. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  291. package/src/skf-export-skill/references/load-skill.md +88 -62
  292. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  293. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  294. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  295. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  296. package/src/skf-export-skill/references/package.md +15 -73
  297. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  298. package/src/skf-export-skill/references/result-envelope.md +31 -16
  299. package/src/skf-export-skill/references/summary.md +41 -17
  300. package/src/skf-export-skill/references/token-report.md +4 -12
  301. package/src/skf-export-skill/references/update-context.md +118 -237
  302. package/src/skf-forger/SKILL.md +61 -37
  303. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  304. package/src/skf-forger/customize.toml +43 -0
  305. package/src/skf-forger/references/pipeline-mode.md +73 -18
  306. package/src/skf-forger/scripts/forge-status.py +299 -0
  307. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  308. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  309. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  310. package/src/skf-quick-skill/SKILL.md +54 -53
  311. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  312. package/src/skf-quick-skill/customize.toml +51 -20
  313. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  314. package/src/skf-quick-skill/references/compile.md +30 -31
  315. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  316. package/src/skf-quick-skill/references/finalize.md +69 -18
  317. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  318. package/src/skf-quick-skill/references/health-check.md +5 -3
  319. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  320. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  321. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  322. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  323. package/src/skf-refine-architecture/SKILL.md +43 -53
  324. package/src/skf-refine-architecture/customize.toml +49 -20
  325. package/src/skf-refine-architecture/references/compile.md +97 -54
  326. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  327. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  328. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  329. package/src/skf-refine-architecture/references/improvements.md +14 -20
  330. package/src/skf-refine-architecture/references/init.md +98 -57
  331. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  332. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  333. package/src/skf-refine-architecture/references/report.md +65 -54
  334. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  335. package/src/skf-rename-skill/SKILL.md +19 -54
  336. package/src/skf-rename-skill/customize.toml +32 -26
  337. package/src/skf-rename-skill/references/execute.md +230 -184
  338. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  339. package/src/skf-rename-skill/references/health-check.md +1 -7
  340. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  341. package/src/skf-rename-skill/references/report.md +63 -52
  342. package/src/skf-rename-skill/references/select.md +197 -115
  343. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  344. package/src/skf-setup/SKILL.md +26 -26
  345. package/src/skf-setup/customize.toml +14 -10
  346. package/src/skf-setup/references/auto-index.md +50 -63
  347. package/src/skf-setup/references/ccc-index.md +29 -90
  348. package/src/skf-setup/references/detect-and-tier.md +58 -67
  349. package/src/skf-setup/references/health-check.md +2 -0
  350. package/src/skf-setup/references/invocation-contract.md +14 -0
  351. package/src/skf-setup/references/report.md +43 -166
  352. package/src/skf-setup/references/tier-rules.md +5 -5
  353. package/src/skf-setup/references/write-config.md +34 -57
  354. package/src/skf-test-skill/SKILL.md +17 -57
  355. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  356. package/src/skf-test-skill/customize.toml +27 -13
  357. package/src/skf-test-skill/references/coherence-check.md +134 -62
  358. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  359. package/src/skf-test-skill/references/coverage-check.md +217 -254
  360. package/src/skf-test-skill/references/detect-mode.md +5 -5
  361. package/src/skf-test-skill/references/external-validators.md +94 -82
  362. package/src/skf-test-skill/references/health-check.md +6 -7
  363. package/src/skf-test-skill/references/init.md +163 -88
  364. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  365. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  366. package/src/skf-test-skill/references/report.md +162 -142
  367. package/src/skf-test-skill/references/score.md +80 -107
  368. package/src/skf-test-skill/references/scoring-rules.md +41 -155
  369. package/src/skf-test-skill/references/source-access-protocol.md +19 -92
  370. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  371. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  372. package/src/skf-test-skill/scripts/build-result-context.py +287 -0
  373. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  374. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  375. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  376. package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
  377. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  378. package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
  379. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  380. package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
  381. package/src/skf-test-skill/scripts/score-signatures.py +459 -0
  382. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  383. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  384. package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
  385. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  386. package/src/skf-update-skill/SKILL.md +38 -25
  387. package/src/skf-update-skill/customize.toml +27 -12
  388. package/src/skf-update-skill/references/detect-changes.md +233 -222
  389. package/src/skf-update-skill/references/gap-driven.md +336 -0
  390. package/src/skf-update-skill/references/health-check.md +31 -5
  391. package/src/skf-update-skill/references/init.md +250 -84
  392. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  393. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  394. package/src/skf-update-skill/references/merge.md +180 -47
  395. package/src/skf-update-skill/references/re-extract.md +53 -262
  396. package/src/skf-update-skill/references/report.md +100 -35
  397. package/src/skf-update-skill/references/write.md +331 -149
  398. package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
  399. package/src/skf-verify-stack/SKILL.md +23 -42
  400. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  401. package/src/skf-verify-stack/customize.toml +55 -22
  402. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  403. package/src/skf-verify-stack/references/coverage.md +67 -64
  404. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  405. package/src/skf-verify-stack/references/init.md +107 -62
  406. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  407. package/src/skf-verify-stack/references/integrations.md +106 -65
  408. package/src/skf-verify-stack/references/report.md +59 -42
  409. package/src/skf-verify-stack/references/requirements.md +32 -9
  410. package/src/skf-verify-stack/references/synthesize.md +53 -39
  411. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  412. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  413. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  414. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  415. package/tools/cli/commands/install.js +11 -4
  416. package/tools/cli/commands/status.js +15 -3
  417. package/tools/cli/commands/update.js +11 -4
  418. package/tools/cli/lib/tool-check.js +320 -0
  419. package/tools/cli/lib/version-check.js +22 -9
  420. package/tools/cli/skf-cli.js +4 -1
  421. package/.gitattributes +0 -16
  422. package/.nvmrc +0 -1
  423. package/docs/_internal/RELEASING.md +0 -679
  424. package/release-audits/v1.0.0-launch-audit.md +0 -728
  425. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  426. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  427. package/src/skf-campaign/manifest.yaml +0 -15
  428. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  429. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  430. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  431. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  432. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  433. package/src/skf-update-skill/references/validate.md +0 -124
  434. package/tools/validate-docs-drift.js +0 -233
  435. package/tools/validate-docs-links.js +0 -665
@@ -1,16 +1,50 @@
1
1
  ---
2
2
  nextStepFile: 'detect-mode.md'
3
- outputFile: '{forge_version}/test-report-{skill_name}-{run_id}.md'
3
+ # §6c creates the report under this hidden name and binds `{report_file}` to
4
+ # it; report.md §4c gives it its public name test-report-{skill_name}-{run_id}.md
5
+ # once its checks pass, a blocked run's included.
6
+ outputFile: '{forge_version}/.skf-test-report-{skill_name}-{run_id}.md'
4
7
  templateFile: '{testReportTemplatePath}'
5
8
  sidecarFile: '{sidecar_path}/forge-tier.yaml'
6
9
  skillsOutputFolder: '{skills_output_folder}'
10
+ # Resolve `{emitEnvelopeHelper}` to the first existing path at §0, before
11
+ # anything else: every HALT prints its headless envelope through it, and
12
+ # report.md §4c writes the result contract with it.
13
+ emitEnvelopeProbeOrder:
14
+ - '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
15
+ - '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
7
16
  # frontmatterScript resolves deterministically by probing two candidate
8
17
  # paths from `{project-root}` in order. There is NO silent manual fallback —
9
18
  # if neither candidate exists, the step HALTs with a diagnostic.
10
19
  frontmatterScriptProbeOrder:
11
20
  - '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
12
21
  - '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
13
- versionPathsKnowledge: 'knowledge/version-paths.md'
22
+ # Resolve `{skillInventoryHelper}` to the first existing path. §2 runs its
23
+ # `resolve` command to choose the version under test and bind its paths,
24
+ # and runs it with `--skill` before a flat skill moves: only a flat skill
25
+ # whose metadata.json carries an SKF marker (`flat_skf`) is migrated. §1
26
+ # and §2 run it with `--near` to list the testable skills.
27
+ skillInventoryProbeOrder:
28
+ - '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
29
+ - '{project-root}/src/shared/scripts/skf-skill-inventory.py'
30
+ # Resolve `{versionPathsKnowledge}` to the first existing path. §2 loads only
31
+ # its "Migration: Flat to Versioned" section, and only to migrate a flat skill.
32
+ versionPathsKnowledgeProbeOrder:
33
+ - '{project-root}/_bmad/skf/knowledge/version-paths.md'
34
+ - '{project-root}/src/knowledge/version-paths.md'
35
+ # Resolve `{checkWorkspaceDriftHelper}` to the first existing path; §5b HALTs
36
+ # if neither exists. It runs the workspace drift guard, once per repository
37
+ # of a stack skill.
38
+ checkWorkspaceDriftProbeOrder:
39
+ - '{project-root}/_bmad/skf/shared/scripts/skf-check-workspace-drift.py'
40
+ - '{project-root}/src/shared/scripts/skf-check-workspace-drift.py'
41
+ # Resolve `{runLockHelper}` to the first existing path; §6a HALTs if neither
42
+ # exists. §6a takes the run lock and the run id through it, and it stays
43
+ # bound for the rest of the run: report.md §4c renews the lock through it,
44
+ # and every later HALT and report.md §7 release it.
45
+ runLockProbeOrder:
46
+ - '{project-root}/_bmad/skf/shared/scripts/skf-run-lock.py'
47
+ - '{project-root}/src/shared/scripts/skf-run-lock.py'
14
48
  ---
15
49
 
16
50
  <!-- Config: communicate in {communication_language}. -->
@@ -21,24 +55,49 @@ versionPathsKnowledge: 'knowledge/version-paths.md'
21
55
 
22
56
  Discover and validate the target skill, load forge tier state to determine analysis depth, and create the test report document from template.
23
57
 
24
- ### 1. Receive Skill Path
58
+ **Halt envelope.** Every HALT in this step names its `halt_reason` and phase and carries exit code 1. Once §6a took the run lock, it releases the lock first, whatever the release prints: from `{project-root}`, run `uv run {runLockHelper} release --lock "{forge_version}/.test-skill.lock" --owner "{run_owner}"`. In headless mode it prints its envelope through `{emitEnvelopeHelper}` (§0). Until §6b has created the run folder, pass the payload on stdin, adding `"skill_name": "{skill_name}"` once §1 named the skill and `"path"` when the halt names one:
25
59
 
26
- If skill path was provided as workflow argument, use it directly.
60
+ ```bash
61
+ uv run {emitEnvelopeHelper} emit-halt --workflow skf-test-skill --target stderr <<'SKF_TS_HALT'
62
+ {"phase": "<phase>", "reason": "<the halt message>", "halt_reason": "<halt_reason>"}
63
+ SKF_TS_HALT
64
+ ```
65
+
66
+ Once §6b has created the run folder, write the same object, with `"report_path": "{report_file}"` once §6c created the report, to `{run_dir}/halt.json` and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-test-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`. The emitter sets `status: "error"`, derives `exit_code` from `halt_reason` and stamps `run_id`. Display the line it prints, as the run's last line, then stop. If the emitter exits non-zero or prints no line, display the halt message alone. An interactive HALT displays its message and emits nothing. `references/invocation-contract.md` lists every `halt_reason`.
67
+
68
+ ### 0. Resolve the Envelope Emitter
69
+
70
+ Resolve `{emitEnvelopeHelper}` ← first existing path in `{emitEnvelopeProbeOrder}`, before any prompt or check; it stays bound for the run. If neither path exists, HALT (`halt_reason: "helper-missing"`, phase `init:emitter`) and display only: "**test-skill cannot run without `skf-emit-result-envelope.py`, which is not installed.** Re-install SKF." With no emitter, this HALT prints no envelope.
71
+
72
+ ### 0b. Check the Runtime
73
+
74
+ Every step runs SKF's helpers through `uv run`, which also installs the dependencies each script declares (PEP 723); bare `python3` ignores them, so a helper such as the frontmatter validator fails on its PyYAML import. Confirm that `python3` and `uv` are both on `$PATH` (`command -v python3` and `command -v uv`). If either is missing, HALT (`halt_reason: "runtime-missing"`, phase `init:runtime`): "**test-skill needs `{the missing tool}`, which is not on your PATH.** Install it (`uv`: <https://docs.astral.sh/uv/getting-started/installation/>; `python3`: <https://www.python.org/downloads/>; both are documented runtime prerequisites, see `docs/getting-started.md`), then re-run." Nothing is written. The emitter needs only the standard library, so when `uv` is the missing tool the envelope command runs with `python3` in place of `uv run`; with both missing, display the halt message alone.
75
+
76
+ ### 1. Receive the Skill Name
77
+
78
+ Bind `{skill_name}` from the workflow argument: a skill name, or a path inside `{skillsOutputFolder}`, which names the skill folder it lies in (its first folder under `{skillsOutputFolder}`).
27
79
 
28
80
  **Recognized flags on the invocation:**
29
- - `--allow-workspace-drift` — bypass the section 5b pre-flight guard that halts when local workspace HEAD does not match `metadata.source_commit`. Store `allow_workspace_drift: true` in workflow context when present. No effect when `source_commit` is unpinned or the source is not a git working tree.
81
+ - `--allow-workspace-drift`: bypass the section 5b pre-flight guard that halts when local workspace HEAD does not match `metadata.source_commit`. Store `allow_workspace_drift: true` in workflow context when present; §5b decides whether drift was overridden and records it in the report frontmatter (`workspaceDrift`), which the later steps read. No effect when `source_commit` is unpinned or the source is not a git working tree.
30
82
  - `--no-discovery` — skip the §4b Discovery Testing block in step 6 (report). Store `no_discovery: true` in workflow context when present.
31
83
  - `--no-health-check` — skip the §7 health-check dispatch in step 6 (report). Store `no_health_check: true` in workflow context when present.
32
- - `--tier=<Quick|Forge|Forge+|Deep>` — bypass the §4 forge-tier.yaml sidecar HALT. Store `tier_flag: '<value>'` in workflow context when present; §4 will set `detected_tier` directly from this value and skip the sidecar probe.
33
- - `--threshold=<N>` — override the pass threshold for this run. Consumed by `references/score.md` §1; CLI wins over per-pipeline defaults (§1b) and the `workflow.default_threshold` scalar.
84
+ - `--tier=<Quick|Forge|Forge+|Deep>`: bypass the §4 forge-tier.yaml sidecar HALT. Store `tier_flag: '<value>'` in workflow context when present; §4 will set `detected_tier` directly from this value and skip the sidecar probe. A value that is not one of the four (case-sensitive) HALTs here, before anything runs (`halt_reason: "input-invalid"`, phase `init:flags`): "Error: --tier=<value> is not one of Quick, Forge, Forge+, Deep".
85
+ - `--threshold=<N>`: override the pass threshold for this run. §1b resolves it: CLI wins over per-pipeline defaults and the `workflow.default_threshold` scalar.
86
+ - `--discovery-catalog=all`: widen the discovery catalog of step 6 §4b.0. Store `discovery_catalog_all: true` in workflow context when present.
34
87
 
35
- If no path provided, ask:
88
+ **No skill name given.** In `{headless_mode}` there is no one to ask: HALT (`halt_reason: "input-missing"`, phase `init:skill-name`, no `skill_name` in the payload): "**test-skill needs a skill name.** Re-run with the name of the skill to test." Interactively, list the skills there are to test, then ask. Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}` (§2 uses it too; if neither path exists, HALT (`halt_reason: "helper-missing"`, phase `init:skill-name`) with §2's not-found message) and run:
89
+
90
+ ```bash
91
+ uv run {skillInventoryHelper} {skillsOutputFolder} --near ""
92
+ ```
93
+
94
+ Show each `near[]` entry's `name` and `active_version` (`no active version` when it is null); when the helper exits non-zero, or `near[]` is absent or empty, show the create-skill line below instead. Then ask:
36
95
 
37
96
  "**Which skill would you like to test?**
38
97
 
39
- Provide the skill path or name. I'll search in `{skillsOutputFolder}`.
98
+ {the list above, or: No skill SKF generated is in `{skillsOutputFolder}` yet: run the **create-skill** workflow first.}
40
99
 
41
- **Path or name:**"
100
+ **Skill name:**"
42
101
 
43
102
  ### 1b. Resolve Per-Pipeline Quality Threshold
44
103
 
@@ -51,20 +110,47 @@ If `{pipeline_alias}` is set in the workflow data context (forwarded by the forg
51
110
  | `forge-quick` | 80 |
52
111
  | `campaign` | 90 |
53
112
 
54
- - **If `{pipeline_alias}` is present AND found in the table:** store the corresponding value as `{pipeline_default_threshold}` in workflow context. This variable is consumed by `references/score.md` §1 as a precedence layer between CLI `--threshold` and `{defaultThreshold}`.
55
- - **If `{pipeline_alias}` is present but NOT in the table:** `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
56
- - **If `{pipeline_alias}` is absent** (standalone TS invocation, not running inside a pipeline): `{pipeline_default_threshold}` remains unset. Score.md falls through to `{defaultThreshold}`.
113
+ - **If `{pipeline_alias}` is present AND found in the table:** store the corresponding value as `{pipeline_default_threshold}` in workflow context. It is the precedence layer between CLI `--threshold` and `{defaultThreshold}` below.
114
+ - **If `{pipeline_alias}` is present but NOT in the table:** `{pipeline_default_threshold}` remains unset, and the threshold falls through to `{defaultThreshold}`.
115
+ - **If `{pipeline_alias}` is absent** (standalone TS invocation, not running inside a pipeline): `{pipeline_default_threshold}` remains unset, and the threshold falls through to `{defaultThreshold}`.
116
+
117
+ **Resolve the pass threshold once, here (precedence: CLI > pipeline default > scalar > bundled fallback):**
118
+
119
+ 1. If the workflow received `--threshold=<N>` on invocation, use that integer as `effective_threshold` (CLI wins). Set `threshold_source` = `"CLI override ({N}%)"`.
120
+ 2. Else if `{pipeline_default_threshold}` is set (the table above), use it as `effective_threshold`. Set `threshold_source` = `"pipeline default ({pipeline_alias} → {N}%)"`.
121
+ 3. Else if the resolved `{defaultThreshold}` workflow-context variable (from SKILL.md On Activation §3: the `workflow.default_threshold` scalar, default `80`) is set, use it as `effective_threshold`. Set `threshold_source` = `"workflow default ({N}%)"`.
122
+ 4. Else fall back to `80` (the bundled default: this branch should be unreachable when SKILL.md resolution ran correctly, but keeps the step robust if customize.toml resolution failed silently). Set `threshold_source` = `"bundled fallback (80%)"`.
123
+
124
+ §6c writes both into the report frontmatter (`threshold`, `thresholdSource`), and `references/score.md` §1 reads them from there: a long run's context may lose them, the report keeps them.
57
125
 
58
126
  ### 2. Validate Skill Exists (version-aware)
59
127
 
60
- Resolve the skill path using version-aware resolution (see `{versionPathsKnowledge}`):
128
+ The inventory helper chooses the version to test and binds its paths by the Reading Workflows rules of the version-paths knowledge (the Manifest-lag guard included), so this step never walks the export manifest or the `active` link by hand. Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}` (unless §1 already did) and run:
129
+
130
+ ```bash
131
+ uv run {skillInventoryHelper} resolve {skillsOutputFolder} --skill {skill_name} --forge-data-folder {forge_data_folder}
132
+ ```
133
+
134
+ Bind from its `resolve` object:
135
+
136
+ - `{resolved_version}` ← `chosen_version`
137
+ - `{resolved_skill_package}` ← `skill_package`
138
+ - `{forge_version}` ← `forge_version`: the version's folder in the forge data, where this run writes its report, its result files and its lock
139
+ - `{forge_provenance_map}` ← `paths.provenance_map.path` and `{forge_evidence_report}` ← `paths.evidence_report.path`: the version folder's file when it exists, else the flat copy an older skill may still keep in `{forge_data_folder}/{skill_name}/`, else null. Every later step reads the provenance map and the evidence report through these two bindings, except coverage-check §4c: it checks the version folder's own map, whose lines update-skill moves.
140
+
141
+ Log each entry of `resolve.errors` (a broken `active` link, for example), and `resolve.manifest_error` when it is set (an export manifest that cannot be read), as an Info note, then act on `reason`:
142
+
143
+ 1. `manifest-and-link`, `manifest` or `link`: the export manifest or the `active` link names the version, and its package is on disk. Continue.
144
+ 2. `manifest-lags-link`: the `active` link names a version the manifest has not caught up with (the Manifest-lag guard). Continue, and emit the helper's `detail` as an Info note.
145
+ 3. If neither: fall back to the flat path `{skillsOutputFolder}/{skill_name}/` (`reason` is `flat-layout`: `SKILL.md` sits at the skill folder root, with no version folder yet). Check that SKF generated it before anything moves:
146
+ - Run `uv run {skillInventoryHelper} {skillsOutputFolder} --skill {skill_name}`, and bind `{group_flat_skf}` ← `skills[0].flat_skf` and `{group_errors}` ← `skills[0].errors`.
147
+ - **`{group_flat_skf}` is true:** auto-migrate per the migration rules of `{versionPathsKnowledge}` (resolve it ← first existing path in `{versionPathsKnowledgeProbeOrder}` and load only its "Migration: Flat to Versioned" section), then run the `resolve` command above again and bind its values anew: they now name the version folder.
148
+ - **Otherwise** (`{group_flat_skf}` is false, the status is not `ok`, `skills[]` has no entry, or no helper candidate resolves): do not migrate. HALT before anything moves (`halt_reason: "not-skf-output"`, phase `init:ownership`) with this message: "**`{skill_name}` is not SKF output: nothing was moved.** `{skillsOutputFolder}/{skill_name}/SKILL.md` has no SKF marker in the `metadata.json` beside it, so SKF will not move or test it. A shared `{skillsOutputFolder}` is supported: SKF leaves the skills it did not generate alone, so manage `{skill_name}` yourself. Only if `{skillsOutputFolder}` holds a module's own source rather than skills, set `skills_output_folder` in `{project-root}/_bmad/skf/config.yaml` to a folder of its own, move your SKF skills there and re-run `/skf-setup`." When there is another reason, show it in place of the marker sentence: `{group_errors}` when it is non-empty (for example, the folder is a link, which SKF never moves), the helper's `error` when the status is not `ok`, and, when no helper candidate resolved, that SKF could not check the marker because `skf-skill-inventory.py` is missing, so re-install SKF.
149
+ 4. `missing`, or the helper stops with `SKILL_NOT_FOUND` or `DIR_NOT_FOUND`: no version of the skill is on disk, or the name names none. Take the not-found branch below.
150
+ 5. `newest-on-disk`: version folders exist, but neither the export manifest nor a working `active` link names one. The Reading Workflows rules test only a version one of them names, so take the SKILL.md error below.
151
+ 6. Any other error, or no JSON: HALT (`halt_reason: "helper-failed"`, phase `init:resolve`) with the helper's stderr message. Nothing is written.
61
152
 
62
- 1. Read `{skillsOutputFolder}/.export-manifest.json` and look up the skill name in `exports` to get `active_version`
63
- 2. **Manifest-lag guard.** If the skill is in the manifest, also read the `active` symlink target at `{skillsOutputFolder}/{skill_name}/active`. If that symlink resolves to a *different* version than `active_version`, prefer the **symlink target** as `{resolved_version}` and emit an Info note: "manifest active_version {M} lags the active symlink {N} — testing the symlink target (the just-forged version); run export-skill to reconcile the manifest." This is the canonical SS→TS→EX case: create-stack-skill flipped `active` to the new version, but the manifest only advances when export-skill runs — so a bare manifest-first resolution would test the *previously exported* version and report a PASS for the wrong version (silent false confidence). When the symlink matches `active_version` (or no `active` symlink exists), use `active_version`. See `{versionPathsKnowledge}` "Reading Workflows".
64
- 3. If found: resolve to `{skill_package}` = `{skillsOutputFolder}/{skill_name}/{resolved_version}/{skill_name}/`
65
- 4. If not in manifest: check for `active` symlink at `{skillsOutputFolder}/{skill_name}/active` — resolve to `{skill_group}/active/{skill_name}/`
66
- 5. If neither: fall back to flat path `{skillsOutputFolder}/{skill_name}/`. If SKILL.md exists at the flat path, auto-migrate per `{versionPathsKnowledge}` migration rules
67
- 6. Store the resolved path as `{resolved_skill_package}`
153
+ If no helper candidate resolves, SKF can neither choose the version nor check a flat skill's marker. Nothing moves: when `{skillsOutputFolder}/{skill_name}/SKILL.md` exists, take step 3's Otherwise branch; else HALT (`halt_reason: "helper-missing"`, phase `init:resolve`) with "Error: cannot locate skf-skill-inventory.py at `{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py` or `{project-root}/src/shared/scripts/skf-skill-inventory.py`. Install the SKF module or run from a development checkout with src/ present."
68
154
 
69
155
  Check that the skill package contains required files:
70
156
 
@@ -72,18 +158,17 @@ Check that the skill package contains required files:
72
158
  - `{resolved_skill_package}/SKILL.md` — the skill documentation
73
159
  - `{resolved_skill_package}/metadata.json` — skill metadata
74
160
 
75
- **If SKILL.md missing:**
76
- "**Error: SKILL.md not found at `{resolved_skill_package}/SKILL.md`**
161
+ **Not found** (step 4): the name may be mistyped or partial, so list the skills there are to test, closest to it first, rather than sending the user to create a skill that already exists:
77
162
 
78
- This skill has not been created yet. Run the **create-skill** workflow first."
163
+ ```bash
164
+ uv run {skillInventoryHelper} {skillsOutputFolder} --near "{skill_name}"
165
+ ```
79
166
 
80
- **Headless envelope (if `{headless_mode}`):** emit to **stderr**:
167
+ Its `near[]` lists every skill SKF generated that has a SKILL.md, the `close` names first (one name holds the other, or they are similar). Say "**No version of `{skill_name}` is on disk.** {the helper's `detail` or `error`}", then show each `close` entry's `name` and `active_version`, or, when none is close, every entry, adding "If `{skill_name}` is a new skill, run the **create-skill** workflow first." When the helper exits non-zero, or `near[]` is absent or empty (a `{skillsOutputFolder}` that does not exist, say), show no list, only that create-skill line. Interactively, ask for the skill name again and run this section anew with it. In `{headless_mode}`, HALT (`halt_reason: "target-inaccessible"`, phase `init:resolve`) with that message, the close names written as "Did you mean: {names}?".
81
168
 
82
- ```
83
- SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":null,"score":null,"threshold":null,"report_path":null,"next_workflow":null,"exit_code":1,"halt_reason":"target-inaccessible"}
84
- ```
169
+ **If SKILL.md missing** (or step 5 found no version to test): HALT (`halt_reason: "target-inaccessible"`, phase `init:resolve`) with "**Error: SKILL.md not found at `{resolved_skill_package}/SKILL.md`**
85
170
 
86
- HALT — do not proceed.
171
+ This skill has not been created yet. Run the **create-skill** workflow first." When step 5 applies, say instead: "**Error: no version of `{skill_name}` is named for testing.** {the helper's `detail`}. Point `{skillsOutputFolder}/{skill_name}/active` at the version to test, then re-run."
87
172
 
88
173
  **If metadata.json missing:**
89
174
  "**Warning:** metadata.json not found. Proceeding with limited metadata. Some checks may be skipped."
@@ -97,7 +182,7 @@ HALT — do not proceed.
97
182
 
98
183
  Use the first path that exists as `{frontmatterScript}`. There is no manual fallback.
99
184
 
100
- **If neither path exists, HALT** with the diagnostic below. test-skill is a quality gate; without the deterministic validator it cannot produce a trustworthy frontmatter verdict, and a silent manual check can miss subtle spec drift. The missing helper must be restored before testing continues:
185
+ **If neither path exists, HALT** (`halt_reason: "helper-missing"`, phase `init:frontmatter`) with the diagnostic below. test-skill is a quality gate; without the deterministic validator it cannot produce a trustworthy frontmatter verdict, and a silent manual check can miss subtle spec drift. The missing helper must be restored before testing continues:
101
186
 
102
187
  ```
103
188
  Error: cannot locate skf-validate-frontmatter.py at either of:
@@ -110,28 +195,24 @@ SKF module (`skf init`) or run from a development checkout with src/ present.
110
195
 
111
196
  Do not proceed. No partial test report is written.
112
197
 
113
- **3b. Python runtime probe (before invoking the validator).** Confirm both `python3` and `uv` are on `$PATH` (`command -v python3` and `command -v uv`). Both are required: `uv run` shells through to `python3` and honors the script's PEP 723 PyYAML dependency declaration that bare `python3` ignores (bare `python3` fails with `ModuleNotFoundError: No module named 'yaml'` on a fresh interpreter — `docs/getting-started.md` documents uv as the runtime prereq for exactly this). If either is missing, set `analysis_confidence: degraded` in workflow context and carry a **score cap** into step 5: `capped_score = threshold - 1` → forces auto-FAIL until the runtime is restored. Record the reason in evidence-report and the test report frontmatter (`analysisConfidence: degraded`, `toolingStatus: python3-missing` or `uv-missing` as appropriate). `uv` is a documented runtime prerequisite — see `docs/getting-started.md` for install instructions.
114
-
115
- **3c. Run the validator (30s timeout — the deterministic validator should finish in <1s; the cap only guards against runaway python).**
198
+ **3b. Run the validator (30s timeout: the deterministic validator should finish in under 1s, and the cap only guards against runaway python).**
116
199
 
117
200
  ```bash
118
201
  timeout 30s uv run {frontmatterScript} {resolved_skill_package}/SKILL.md --skill-dir-name {skill_name}
119
202
  ```
120
203
 
121
204
  If the command trips the 30s wall-clock (exit code `124`), set
122
- `analysis_confidence: degraded` and `toolingStatus: frontmatter-validator-timeout`
123
- in workflow context, apply the step 5 tooling-degraded cap (score capped at
124
- `threshold - 1` → auto-FAIL), and record the reason in evidence-report.
205
+ `tooling_status: frontmatter-validator-timeout` in workflow context; otherwise
206
+ set `tooling_status: ok`. Tooling health lives only there: `analysis_confidence`
207
+ names the source access coverage-check resolves, so neither overwrites the
208
+ other. Step 5 passes `tooling_status` to its scoring script, whose Cap 1 fails
209
+ any run whose tooling status is not `ok`, at every threshold.
125
210
 
126
211
  Parse the JSON output. Treat each `status` value explicitly:
127
212
 
128
213
  - `status: "pass"` — continue silently.
129
214
  - `status: "warn"` — display the warning below, log each issue as a pre-check finding, and continue with testing. Frontmatter issues surface in the gap report alongside coverage/coherence findings.
130
- - `status: "fail"` — **HALT with auto-FAIL.** Frontmatter failure means the skill will be rejected by `npx skills add` and `npx skill-check check`; shipping it would produce a false PASS downstream. Write the halt note into evidence-report and exit non-zero. **Headless envelope (if `{headless_mode}`):** emit to **stderr** before halting. The output document does not exist yet (created in §6), so `report_path` is `null` — matching the other pre-report init HALTs (target-inaccessible, forge-tier-missing, workspace-drift, another-run-active). A frontmatter-invalid target is the most common failure this gate exists to catch, so a headless orchestrator must be able to branch on it (route to update-skill) rather than see an unlabelled non-zero exit:
131
-
132
- ```
133
- SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":null,"score":null,"threshold":null,"report_path":null,"next_workflow":null,"exit_code":1,"halt_reason":"frontmatter-invalid"}
134
- ```
215
+ - `status: "fail"`: **HALT** (`halt_reason: "frontmatter-invalid"`, phase `init:frontmatter`) with the message below. Frontmatter failure means the skill will be rejected by `npx skills add` and `npx skill-check check`; shipping it would produce a false PASS downstream. The report does not exist yet (§6c creates it), so the envelope carries no `report_path`, like every HALT before §6c. A frontmatter-invalid target is the most common failure this gate exists to catch, so a headless orchestrator branches on its `halt_reason` (route to update-skill) rather than on an unlabelled failure.
135
216
 
136
217
  ```
137
218
  **Warning/Error: SKILL.md frontmatter is non-compliant with agentskills.io specification.**
@@ -143,7 +224,7 @@ This skill will fail `npx skills add` and `npx skill-check check`. {If warn:} Co
143
224
 
144
225
  ### 4. Load Forge Tier State
145
226
 
146
- **`--tier=<...>` flag bypass (precedes the sidecar probe).** If `tier_flag` is set in workflow context (from §1's `--tier=<Quick|Forge|Forge+|Deep>` flag), validate the value against the allowed set. On valid match: set `detected_tier` directly to the flag's value, leave `ast_grep`/`gh_cli`/`qmd` availability flags unset (downstream steps treat unset as "unknown" — analysis proceeds without tool-specific enrichment), log Info note "tier — supplied via --tier flag, sidecar bypassed", and SKIP the sidecar probe and HALT below (jump straight to §4b "Apply Tier Override"). On invalid value (not one of the four), HALT with "Error: --tier=<value> is not one of Quick, Forge, Forge+, Deep".
227
+ **`--tier=<...>` flag bypass (precedes the sidecar probe).** If `tier_flag` is set in workflow context (from §1's `--tier=<Quick|Forge|Forge+|Deep>` flag, which §1 checked): set `detected_tier` directly to the flag's value, leave `ast_grep`/`gh_cli`/`qmd` availability flags unset (downstream steps treat unset as "unknown": analysis proceeds without tool-specific enrichment), log Info note "tier: supplied via --tier flag, sidecar bypassed", and SKIP the sidecar probe and HALT below (jump straight to §4b "Apply Tier Override").
147
228
 
148
229
  **Otherwise (no `--tier` flag):** Read `{sidecarFile}` to determine available analysis depth.
149
230
 
@@ -151,16 +232,7 @@ This skill will fail `npx skills add` and `npx skill-check check`. {If warn:} Co
151
232
  - Read `tier` value (Quick, Forge, Forge+, or Deep)
152
233
  - Read tool availability flags (ast_grep, gh_cli, qmd)
153
234
 
154
- **If forge-tier.yaml missing:**
155
- "**Cannot proceed.** forge-tier.yaml not found at `{sidecarFile}`. Please run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep), or re-run with `--tier=<Quick|Forge|Forge+|Deep>` to bypass the sidecar."
156
-
157
- **Headless envelope (if `{headless_mode}`):** emit to **stderr**:
158
-
159
- ```
160
- SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":null,"score":null,"threshold":null,"report_path":null,"next_workflow":null,"exit_code":1,"halt_reason":"forge-tier-missing"}
161
- ```
162
-
163
- HALT — do not proceed.
235
+ **If forge-tier.yaml missing:** HALT (`halt_reason: "forge-tier-missing"`, phase `init:forge-tier`, `"path": "{sidecarFile}"`) with "**Cannot proceed.** forge-tier.yaml not found at `{sidecarFile}`. Please run the **setup** workflow first to configure your forge tier (Quick/Forge/Forge+/Deep), or re-run with `--tier=<Quick|Forge|Forge+|Deep>` to bypass the sidecar."
164
236
 
165
237
  ### 4b. Apply Tier Override (if set)
166
238
 
@@ -176,71 +248,72 @@ Read `metadata.json` to extract:
176
248
  - `source_ref` — pinned ref (tag/branch/`HEAD`) used at extraction time
177
249
  - `generation_date` — when skill was generated
178
250
  - `confidence_tier` — tier used during creation
251
+ - `generated_by`: the SKF workflow that built the skill (coverage-check §2 reads it at Quick tier)
179
252
 
180
253
  If source path override was provided as optional input, use that instead.
181
254
 
182
255
  ### 5b. Verify Workspace HEAD Matches Pinned Commit
183
256
 
184
- Test-skill reads `source_path` during coverage and coherence analysis. If the local workspace has drifted from `metadata.source_commit`, gap and signature-mismatch findings will silently reflect the drifted tree, not the skill's pinned source — producing false positives that downstream update-skill runs may then "repair" by corrupting correct documentation.
257
+ Test-skill reads `source_path` during coverage and coherence analysis. If the local workspace has drifted from `metadata.source_commit`, gap and signature-mismatch findings silently reflect the drifted tree, not the skill's pinned source: false positives that downstream update-skill runs may then "repair" by corrupting correct documentation.
185
258
 
186
- - Resolve `pinned_commit` from `metadata.source_commit`.
187
- - **If `pinned_commit` is null, empty, or `"local"`:** skip the guard; log `workspace_drift_check: skipped (no pinned commit)` and continue to section 6.
188
- - **If `pinned_commit` is a per-repo map (stack skills):** iterate each `{repo_path: commit}` entry — for each repo run `git -C "{repo_path}" rev-parse HEAD` and compare to its pinned commit (accept full-SHA or short-SHA-prefix match). If ANY repo diverges and the user did not pass `--allow-workspace-drift`, HALT with exit status `workspace-drift` listing every mismatched repo (in `{headless_mode}`, emit the same `workspace-drift` stderr envelope shown in the single-tree branch below before halting). On all-match: log `workspace_drift_check: ok (stack, {N} repos verified)` and continue to section 6. This guard must iterate every repo — do not skip stack skills.
189
- - **If `source_path` is not a git working tree** (bare checkout, tarball extract, docs-only source) — detect by `git -C "{source_path}" rev-parse --is-inside-work-tree`, non-zero exit means skip: log `workspace_drift_check: skipped (not a git working tree)` and continue to section 6.
190
- - **Otherwise** run `git -C "{source_path}" rev-parse HEAD` and compare to `pinned_commit`. Accept full-SHA or short-SHA-prefix match (stored pins are often 8-char short hashes — see `src/knowledge/provenance-tracking.md`).
191
- - **On match:** log `workspace_drift_check: ok ({short_sha})` and continue.
192
- - **On mismatch, AND the user did not pass `--allow-workspace-drift`:** HALT with exit status `workspace-drift`. Display:
259
+ The guard runs through `{checkWorkspaceDriftHelper}`, never through `git` commands run by hand. Resolve it ← first existing path in `{checkWorkspaceDriftProbeOrder}`. If neither path exists, HALT (`halt_reason: "helper-missing"`, phase `init:workspace-drift`) with: "Error: cannot locate skf-check-workspace-drift.py at `{project-root}/_bmad/skf/shared/scripts/skf-check-workspace-drift.py` or `{project-root}/src/shared/scripts/skf-check-workspace-drift.py`. Install the SKF module or run from a development checkout with src/ present." No test report is written.
193
260
 
194
- ```
195
- Workspace HEAD does not match the commit this skill was pinned against.
261
+ Run it once per source tree, from `{project-root}`, passing `--allow-drift` only when the user passed `--allow-workspace-drift`:
196
262
 
197
- pinned (metadata.source_commit): {pinned_commit}
198
- pinned ref (metadata.source_ref): {source_ref or "unset"}
199
- workspace HEAD ({source_path}): {head_sha}
263
+ ```bash
264
+ uv run {checkWorkspaceDriftHelper} "{tree}" --pinned-commit "{pinned_commit}" [--source-ref "{source_ref}"] [--allow-drift] --workflow test-skill
265
+ ```
200
266
 
201
- Test-skill verifies against the source the skill was extracted from.
202
- Testing against a drifted tree produces false gaps/mismatches. Re-sync:
267
+ - **One tree:** `{tree}` is `{source_path}`, `{pinned_commit}` is `metadata.source_commit` (an empty string when it is null), and `{source_ref}` is `metadata.source_ref` when it is set.
268
+ - **A stack skill** (`metadata.source_commit` is a `{repo_path: commit}` map): one call per entry, with that repo path as `{tree}`, its commit as `{pinned_commit}`, and the same key's value of `metadata.source_ref` as `{source_ref}` when that is a map too. Check every repo: do not skip stack skills.
269
+ - Make no call for a tree that is not on disk (coverage then reads the provenance map, State 2 of the source access protocol), and log `workspace_drift_check: skipped (no local source at {tree})`. The helper itself skips a tree with no pinned commit (`""` or `"local"`) and one that is not a git working tree.
203
270
 
204
- git -C "{source_path}" checkout {source_ref or pinned_commit}
271
+ Log each call's `log_message`, then act on the statuses together:
205
272
 
206
- Or re-run test-skill with `--allow-workspace-drift` to test against the
207
- current workspace (accepts that findings reflect HEAD, not the pin).
208
- ```
273
+ - **Any `mismatch`** (exit 2): HALT (`halt_reason: "workspace-drift"`, phase `init:workspace-drift`), displaying the `halt_message` of every tree that drifted, verbatim: it names the pinned commit and ref, the workspace HEAD, the `git checkout` that re-syncs the tree, and `--allow-workspace-drift`. The test report has not been created; no partial writes.
274
+ - **Any `overridden`, and no `mismatch`:** carry `workspaceDrift: overridden` into the report frontmatter. The later steps read it there, never the flag: score.md §5 demotes a PASS under drift to `pass-with-drift` and forces `nextWorkflow` to `update-skill`, never `export-skill`, and coverage-check §4c skips its line check. A flag passed on a clean tree changes nothing. Continue.
275
+ - **Otherwise** every call is `ok` or `skipped`: set `workspaceDrift: ok` when at least one tree was checked, else `not-checked`, and continue.
276
+ - A call that exits 1 or prints no JSON could not read its tree (for example, `git` is not installed): log `workspace_drift_check: skipped (helper error: {its stderr})`, count it as `skipped`, and continue.
209
277
 
210
- **Headless envelope (if `{headless_mode}`):** emit to **stderr**:
278
+ ### 6. Create Output Document
211
279
 
212
- ```
213
- SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":null,"score":null,"threshold":null,"report_path":null,"next_workflow":null,"exit_code":1,"halt_reason":"workspace-drift"}
214
- ```
280
+ **6a. Take the run lock and the run id.** A run spans many tool calls and turns, so no process can hold a lock for it: the lock is a file, `{forge_version}/.test-skill.lock`, that names its owner and the time, and it keeps two test-skill runs against this version from writing the same result files. Resolve `{runLockHelper}` ← first existing path in `{runLockProbeOrder}`; it stays bound for the rest of the run. If neither path exists, HALT (`halt_reason: "helper-missing"`, phase `init:run-lock`) with: "Error: cannot locate skf-run-lock.py at `{project-root}/_bmad/skf/shared/scripts/skf-run-lock.py` or `{project-root}/src/shared/scripts/skf-run-lock.py`. Install the SKF module or run from a development checkout with src/ present." From `{project-root}`, run:
215
281
 
216
- Do not proceed. The test report has not been created; no partial writes.
217
- - **On mismatch WITH `--allow-workspace-drift`:** log `workspace_drift_check: overridden (pinned={pinned_commit}, head={head_sha})`, carry the warning into the final report frontmatter (`workspaceDrift: overridden`), and set `allow_workspace_drift: true` in workflow context (consumed by step 5 §5 drift override — a PASS under drift is demoted to `pass-with-drift` and `nextWorkflow` is forced to `update-skill`, never `export-skill`). Continue.
282
+ ```bash
283
+ uv run {runLockHelper} acquire --lock "{forge_version}/.test-skill.lock" --owner "test-skill:{skill_name}"
284
+ ```
218
285
 
219
- ### 6. Create Output Document
286
+ The owner names no run id, so the helper adds one. Bind `{run_id}` ← `run_id` (the UTC time and a random suffix, safe in file names) and `{run_owner}` ← `owner`. Every per-run artifact of this and the later steps carries `{run_id}`, and every release names `{run_owner}` (SKILL.md Workflow Rules).
220
287
 
221
- **6a. Generate `{run_id}`**: a per-run identifier of the form `{YYYYMMDDTHHmmssZ}-{pid}-{rand4}` (UTC timestamp + process PID + 4-char random hex). Store in workflow context. All per-run artifacts in this and subsequent steps must carry this suffix; step 6 verifies `testDate` in the resulting report matches the run's stamp and fail-fast otherwise.
288
+ **6b. Act on the result:**
222
289
 
223
- **6b. Acquire the per-skill test lock**: `flock {forge_version}/.test-skill.lock` for the duration of this run to serialize concurrent `skf-test-skill` invocations against the same skill. If the lock is already held by another run, HALT with "another test-skill run is active for {skill_name}". **Headless envelope (if `{headless_mode}`):** emit to **stderr** before halting:
290
+ - `acquired` is true: the lock is this run's. When `stale_replaced` is not null, a run that ended without releasing its lock left it: log `run lock: replaced the stale lock of {stale_replaced.held_by} (held since {stale_replaced.held_since})` and continue. Then create the run folder, where the later steps keep the files their scripts hand each other, and bind `{run_dir}` ← `{project-root}/_bmad-output/.skf-run/skf-test-skill-{run_id}`:
224
291
 
225
- ```
226
- SKF_TEST_RESULT_JSON: {"status":"error","skill_name":"{skill_name}","verdict":null,"score":null,"threshold":null,"report_path":null,"next_workflow":null,"exit_code":1,"halt_reason":"another-run-active"}
227
- ```
292
+ ```bash
293
+ mkdir -p "{project-root}/_bmad-output/.skf-run/skf-test-skill-{run_id}"
294
+ ```
228
295
 
229
- **6c. Create `{outputFile}` from `{templateFile}`** — use `{forge_version}/test-report-{skill_name}-{run_id}.md` Initial frontmatter:
296
+ If it cannot be created, HALT (`halt_reason: "write-failed"`, phase `init:run-folder`, `"path"` the folder) with the first stderr line, releasing the run lock first as the **Halt envelope** paragraph says and passing the envelope's payload on stdin: there is no folder to stage it in. report.md §7 removes the folder when the run ends; a HALT leaves it for inspection.
297
+ - `acquired` is false (exit 3): another run holds a lock that has not gone stale. HALT (`halt_reason: "another-run-active"`, phase `init:run-lock`) with "**Another test-skill run is active for {skill_name}.** {message}": the helper's `message` names that run, the lock file to delete when no run is active, and the time the lock goes stale. This run holds no lock, so nothing is released.
298
+ - The helper exits 1 or 2, or prints no JSON: HALT (`halt_reason: "helper-failed"`, phase `init:run-lock`) with its stderr message (exit 2: the lock file could not be written, so check that `{forge_version}` is writable). This run holds no lock, so nothing is released.
299
+
300
+ **6c. Create `{outputFile}` from `{templateFile}`**, under the in-progress name `{forge_version}/.skf-test-report-{skill_name}-{run_id}.md` beside the run lock, and bind `{report_file}` ← that path: every later step writes the report there, and report.md §4c gives it its public name `test-report-{skill_name}-{run_id}.md` once its checks pass, a blocked run's included. Until then no consumer can mistake it for a finished report: export-skill and update-skill read only the public name, and `skf-skill-inventory.py` counts a `.skf-` name as SKF's, so a halted run's leftover never turns the version folder foreign. If the file cannot be written, HALT (`halt_reason: "write-failed"`, phase `init:report`, `"path": "{report_file}"`). Initial frontmatter:
230
301
 
231
302
  ```yaml
232
303
  ---
233
304
  workflowType: 'test-skill'
234
305
  skillName: '{skill_name}'
235
- skillDir: '{skill_path}'
306
+ skillDir: '{resolved_skill_package}'
236
307
  runId: '{run_id}'
237
308
  testMode: ''
238
309
  forgeTier: '{detected_tier}'
310
+ hardGate: ''
239
311
  testResult: ''
240
312
  score: ''
241
- threshold: ''
242
- analysisConfidence: '{full|degraded}'
243
- toolingStatus: '{ok|python3-missing|uv-missing|frontmatter-validator-missing|frontmatter-validator-timeout}'
313
+ threshold: '{effective_threshold}%'
314
+ thresholdSource: '{threshold_source}'
315
+ analysisConfidence: ''
316
+ toolingStatus: '{ok|frontmatter-validator-timeout}'
244
317
  workspaceDrift: '{not-checked|ok|overridden}'
245
318
  testDate: '{run_id timestamp ISO-8601 UTC}'
246
319
  stepsCompleted: ['init']
@@ -248,9 +321,11 @@ nextWorkflow: ''
248
321
  ---
249
322
  ```
250
323
 
324
+ `toolingStatus` is the `tooling_status` §3b set: score.md §3a reads it back for Cap 1. `threshold` and `thresholdSource` are the pass threshold §1b resolved and its source: score.md §1 reads them back, and score.md §7 rewrites `threshold` with the threshold the verdict was settled against. `analysisConfidence` stays empty until score.md §7 writes the source access coverage-check resolved: `full`, `provenance-map`, `metadata-only`, `remote-only` or `docs-only`.
325
+
251
326
  ### 7. Report Initialization Status
252
327
 
253
328
  Report initialization to the user: the resolved skill name, path, type, forge tier, and source path. Then proceed to mode detection.
254
329
 
255
- Update stepsCompleted, then load and execute {nextStepFile}.
330
+ `stepsCompleted` already holds `'init'` (§6c created the report with it), so load and execute {nextStepFile}.
256
331
 
@@ -0,0 +1,66 @@
1
+ # Invocation Contract: skf-test-skill
2
+
3
+ The full argument set, gate map, outputs, exit codes and `SKF_TEST_RESULT_JSON` result envelope of `skf-test-skill`, and how a HALT prints its envelope. Interactive runs need none of it: headless automators and pipeline integrators read this file, and each stage's **Halt envelope** paragraph carries what a halting stage needs.
4
+
5
+ ## Invocation Contract
6
+
7
+ | Aspect | Detail |
8
+ |--------|--------|
9
+ | **Inputs** | `skill_name` [required: an interactive run asks for it, a headless run without it halts `input-missing`; a path inside `{skills_output_folder}` names the skill folder it lies in]; optional flags: `--headless` / `-H` (or `headless_mode: true` in preferences.yaml), `--allow-workspace-drift`, `--no-discovery` (skip the report step's Discovery Testing block), `--no-health-check` (skip the health check that ends the run), `--tier=<Quick\|Forge\|Forge+\|Deep>` (bypass the forge-tier.yaml sidecar requirement; any other value halts `input-invalid`), `--threshold=<N>` (override the pass threshold; CLI wins over per-pipeline defaults and the `workflow.default_threshold` scalar), `--discovery-catalog=all` (widen the discovery catalog to the skills in `{project-root}/.claude/skills/` and `{project-root}/_bmad/agents/`) |
10
+ | **Gates** | none: every stage auto-proceeds, and the shared health check ends the run (its review gate defaults to [Q] in headless mode) |
11
+ | **Outputs** | the test report `test-report-{skill_name}-{run_id}.md` in `{forge_version}/`, written as `.skf-test-report-{skill_name}-{run_id}.md` until report.md §4c's checks pass (a run the hard gate blocked included), so a halted run never leaves a partial report under the name export-skill and update-skill read; the result contract `skf-test-skill-result-{YYYYMMDD-HHmmss}.json` and its `skf-test-skill-result-latest.json` copy in `{forge_version}/`, written by the shared emitter; `evidence-report-fallback.md` in `{forge_version}/` when the threshold fallback fires (a score between 80% and the target threshold, no cap fired); the gap ledger `test-findings-{run_id}.json` in `{forge_version}/`; for the run itself, a run folder `{project-root}/_bmad-output/.skf-run/skf-test-skill-{run_id}/` holding the files the scripts hand each other, which report.md §7 removes (a HALT keeps it) |
12
+ | **Concurrency** | init.md §6a takes the run lock `{forge_version}/.test-skill.lock` through `skf-run-lock.py`, and report.md §4c renews it before it publishes the report and writes the result files; while another run holds a fresh lock, the run halts with `halt_reason: "another-run-active"` (exit 1). |
13
+ | **Headless** | No stage waits for an answer: a missing `skill_name` halts `input-missing`, the shared health check queues its findings locally, and the result envelope is the run's last line |
14
+ | **Exit codes** | See "Exit Codes" below |
15
+
16
+ ## Exit Codes
17
+
18
+ Every terminal state settles one exit code, so a headless automator branches on the verdict, and on any HARD HALT, without grepping message text. A skill run cannot set the process exit status of the agent that runs it (`claude -p` exits 0 whatever the run decided), so automators read `exit_code` from the `SKF_TEST_RESULT_JSON` line or from `skf-test-skill-result-latest.json`:
19
+
20
+ | Code | Meaning | Raised by |
21
+ | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
22
+ | 0 | success / PASS | step 6: `testResult: 'pass'` |
23
+ | 1 | error (HARD HALT) | a HALT before a verdict exists, in steps 1 to 6: every `halt_reason` below but `hard-gate-blocked` |
24
+ | 2 | fail / FAIL | step 4c §3: the hard gate blocked the run (`halt_reason: "hard-gate-blocked"`), and step 6 publishes it as a FAIL; step 6: `testResult: 'fail'` |
25
+ | 3 | inconclusive | step 6: `testResult: 'inconclusive'` (distinct from fail so orchestrators can route to manual-review queues) |
26
+ | 4 | pass-with-drift | step 6: `testResult: 'pass-with-drift'` (distinct from clean pass: `--allow-workspace-drift` was in effect and drift was observed; re-test against the pinned commit and refuse export, since exit 0 would wrongly signal a clean pass) |
27
+
28
+ ## Result Envelope (Headless)
29
+
30
+ When `{headless_mode}` is true the run ends with one `SKF_TEST_RESULT_JSON: {...}` line. The shared emitter, `skf-emit-result-envelope.py`, builds every line from a payload the stage stages in `{run_dir}`, stamps `run_id` and `result_path`, and checks it against `{project-root}/_bmad/skf/shared/scripts/schemas/skf-test-result-envelope.v1.json` (`{project-root}/src/shared/scripts/schemas/skf-test-result-envelope.v1.json` in a development checkout), whose descriptions say what each field holds: `status`, `skill_name`, `verdict`, `score`, `threshold`, `report_path`, `next_workflow`, `exit_code`, `halt_reason`, `run_id`, `result_path`, and `threshold_fallback`, `original_threshold`, `warnings` and `error` when they apply. Never type the line yourself.
31
+
32
+ - **A finished run** (report.md §4c): `scripts/build-result-context.py` builds the payload from the report frontmatter, the gap ledger and the scoring output, deriving `verdict`, `exit_code` and `next_workflow` from `testResult`, and the emitter prints the line and writes the result files. `status` is `"success"` for a scored run (PASS, FAIL, INCONCLUSIVE or pass-with-drift) and `"error"` with `halt_reason: "hard-gate-blocked"` for a run the hard gate blocked (verdict `FAIL`, exit 2), which still writes the Gap Report and its FAIL result contract. `next_workflow` is `"export-skill"` only when `verdict` is `PASS`, `"update-skill"` for `FAIL` and `pass-with-drift`, null for `INCONCLUSIVE`. `threshold_fallback` (true) and `original_threshold` appear only when the threshold fallback fired.
33
+ - **A HALT** (below): `status: "error"`, the `halt_reason` of its failure class and the `exit_code` it maps to.
34
+
35
+ The emitter prints a finished run's line on stdout and a halt's or a blocked run's on stderr; the run displays the line either way, so the split is a label only. The line is the run's last line: report.md binds `{result_envelope_line}` to it and the shared health check displays it after everything else (report.md §7 displays it itself under `--no-health-check`), and a HALT displays its line and stops.
36
+
37
+ | `halt_reason` | Raised by |
38
+ | --- | --- |
39
+ | `input-missing` | init.md §1: a headless run given no skill name |
40
+ | `input-invalid` | init.md §1: a `--tier` value that names no tier |
41
+ | `runtime-missing` | init.md §0b: `python3` or `uv` is not on `PATH` |
42
+ | `helper-missing` | an SKF helper the run needs is not installed: init.md §1, §2, §3a, §5b and §6a, coherence-check.md §1 |
43
+ | `helper-failed` | a helper or a script that exits with an error the step cannot correct: init.md §2 and §6b, the coverage and coherence steps' scripts, external-validators.md §5b, step 4c §2, score.md §3c, report.md §4b and §4c |
44
+ | `write-failed` | init.md §6b and §6c (the run folder or the report), report.md §4c (the report's public name, or the result contract the emitter refused twice) |
45
+ | `target-inaccessible` | init.md §2: no version of the skill to test |
46
+ | `not-skf-output` | init.md §2: a flat skill SKF did not generate |
47
+ | `frontmatter-invalid` | init.md §3b |
48
+ | `forge-tier-missing` | init.md §4 |
49
+ | `workspace-drift` | init.md §5b |
50
+ | `another-run-active` | init.md §6b, and the lock renewal in report.md §4c |
51
+ | `inventory-invalid` | coverage-check.md §1a: the documented inventory failed its schema check or spot-check twice |
52
+ | `indeterminate-surface` | coverage-check.md §2b: zero exports, an empty stack surface or a docs-only skill that documents nothing, so no score can be attached |
53
+ | `step-completeness-violation` | step 4c §2 (a stage recorded nothing in the gap ledger) and report.md §4c |
54
+ | `report-anchor-missing` | report.md §4c |
55
+ | `health-check-missing` | report.md §4c, before anything is published |
56
+ | `hard-gate-blocked` | step 4c §3: not a halt, the run ends through report.md and publishes its FAIL |
57
+
58
+ When the emitter itself is not installed (init.md §0), no line can be printed: that HALT displays its message alone.
59
+
60
+ ## Result Files
61
+
62
+ report.md §4c runs the emitter with `--result-dir "{forge_version}"`. It writes the per-run record `skf-test-skill-result-{YYYYMMDD-HHmmss}.json` (UTC; it picks the name, `-2`, `-3` appended when a run already took that second's name) and then its copy `skf-test-skill-result-latest.json`, the stable path pipeline consumers read (a copy, not a symlink), each atomically. The record holds the envelope's fields, plus `skill`, `outputs[]` (the published report), `summary` (`score`, `threshold`, `result` (`PASS`, `PASS_WITH_DRIFT`, `FAIL` or `INCONCLUSIVE`), `testMode`, `activeCategories[]`, `inconclusiveReasons[]` when present, `threshold_fallback`, `original_threshold` and `evidence_report_path` when the threshold fallback fired, `gapCounts` with the gap ledger's gaps by severity, and `hardGate`), `runId` and `healthCheckDispatched`, and the emitter stamps `timestamp`, `run_id`, `headless_decisions` and `warnings` into it. A run the hard gate blocked writes it too, as a FAIL record, so `skf-test-skill-result-latest.json` never holds an earlier run's verdict. A HALT writes no result file.
63
+
64
+ ## Emitting a Halt
65
+
66
+ Every HALT names its `halt_reason` and its phase (`<step>:<check>`, such as `init:runtime` or `report:anchors`) and, in headless mode, prints its envelope through the shared emitter's `emit-halt`, which sets `status: "error"` and derives `exit_code` 1 from the `halt_reason`. Each stage's **Halt envelope** paragraph holds the command, the payload and, once init.md §6a took the run lock, its release.
@@ -11,7 +11,10 @@
11
11
  Execute this check only when both conditions are met:
12
12
 
13
13
  1. Forge tier is **Deep** (tool-gated)
14
- 2. `{forge_data_folder}/{skill_name}/evidence-report.md` exists (data-gated)
14
+ 2. The evidence report exists (data-gated): init.md §2 bound
15
+ `{forge_evidence_report}` (the skill version folder's evidence report, or
16
+ the flat copy an older skill may still keep); the detection contract below
17
+ reads it.
15
18
 
16
19
  If either condition fails, skip silently and proceed to the next section.
17
20
 
@@ -53,7 +56,7 @@ narrative body.
53
56
  ```bash
54
57
  # Extract t2_future_count from frontmatter. Requires a `---` delimiter pair.
55
58
  awk '/^---$/{c++;next} c==1 && /^t2_future_count:/{print $2; exit}' \
56
- {forge_data_folder}/{skill_name}/evidence-report.md
59
+ {forge_evidence_report}
57
60
  ```
58
61
 
59
62
  - **Frontmatter missing OR `t2_future_count` absent** → treat as Case 4 (see