bmad-module-skill-forge 2.1.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 +51 -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 +14 -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 +117 -9
  75. package/src/shared/scripts/skf-detect-docs.py +892 -4
  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 +215 -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 +17 -6
  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 +55 -18
  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 -57
  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 -89
  348. package/src/skf-setup/references/detect-and-tier.md +58 -66
  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 -645
  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,6 +1,10 @@
1
1
  ---
2
2
  nextStepFile: 'health-check.md'
3
3
  outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
4
+ heuristicsFile: 'references/unit-detection-heuristics.md'
5
+ coexistenceFile: 'references/step-auto-scope-coexistence.md'
6
+ splitFile: 'references/step-auto-scope-split.md'
7
+ corporaFile: 'references/step-auto-scope-corpora.md'
4
8
  shapeDetectProbeOrder:
5
9
  - '{project-root}/_bmad/skf/shared/scripts/skf-shape-detect.py'
6
10
  - '{project-root}/src/shared/scripts/skf-shape-detect.py'
@@ -16,9 +20,15 @@ scanManifestsProbeOrder:
16
20
  detectLanguageProbeOrder:
17
21
  - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py'
18
22
  - '{project-root}/src/shared/scripts/skf-detect-language.py'
19
- languageCorporaProbeOrder:
20
- - '{project-root}/_bmad/skf/shared/scripts/skf-language-corpora.py'
21
- - '{project-root}/src/shared/scripts/skf-language-corpora.py'
23
+ detectWorkspacesProbeOrder:
24
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-workspaces.py'
25
+ - '{project-root}/src/shared/scripts/skf-detect-workspaces.py'
26
+ writeSkillBriefProbeOrder:
27
+ - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
28
+ - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
29
+ validateBriefSchemaProbeOrder:
30
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
31
+ - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
22
32
  ---
23
33
 
24
34
  <!-- Config: communicate in {communication_language}. -->
@@ -36,217 +46,165 @@ To automatically scope a repo using shape detection and export surface analysis,
36
46
  - Must produce the same output artifacts as the interactive chain: analysis report + skill-brief.yaml
37
47
  - On unknown shape (exit code 1), fall back to `scan-project.md` (the normal interactive entry point)
38
48
  - On error (exit code 2), HARD HALT with exit code 3 (`resolution-failure`)
49
+ - Every brief goes through the brief writer and the schema gate (§8): a brief either one rejects halts the run before any brief is written
39
50
 
40
51
  ## MANDATORY SEQUENCE
41
52
 
53
+ Every HARD HALT in this step names its exit code, `halt_reason` and phase. When `{headless_mode}` is true it first prints its envelope on stderr through the shared emitter (`{emitEnvelopeHelper}` and `{run_dir}` come from SKILL.md On Activation): stage `{run_dir}/halt.json` as `{"phase": "<phase>", "reason": "<the halt message>", "halt_reason": "<halt_reason>", "mode": "auto"}`, plus `"path"` when the halt names one, then run
54
+
55
+ ```bash
56
+ uv run {emitEnvelopeHelper} emit-halt --workflow skf-analyze-source --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"
57
+ ```
58
+
59
+ and display the line it prints verbatim. Write the payload as valid JSON: in the halt message and `path`, replace each backslash with / and each double quote with a backtick. If the emitter exits non-zero or prints no line, display the halt message alone. An interactive HALT displays its message and emits nothing.
60
+
42
61
  ### 0. URL Type Detection
43
62
 
44
- Read the target URL or path from the pipeline context (`{project_path}` or the first entry in `project_paths[]`).
63
+ **Resolve `{skillInventoryHelper}`** from `{skillInventoryProbeOrder}`; first existing path wins. If neither resolves, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:0`): "`skf-skill-inventory.py` is missing. Re-install SKF." The helper classifies `{project_path}` (the first of `project_paths[]`):
45
64
 
46
- Apply the following heuristic to classify the input:
65
+ ```bash
66
+ uv run {skillInventoryHelper} derive-name --target "{project_path}" --probe-git
67
+ ```
47
68
 
48
- | Input Pattern | Classification | Route |
49
- |---------------|---------------|-------|
50
- | `github.com/{owner}/{repo}` (with or without `.git` suffix, with or without scheme prefix) | GitHub repo | §1 (standard auto-scope) |
51
- | `gitlab.com/...`, `bitbucket.org/...` | Git hosting | §1 (standard auto-scope) |
52
- | Starts with `/`, `./`, `~/`, or `~` | Local filesystem path | §1 (standard auto-scope) |
53
- | Any other `https://` or `http://` URL | Documentation URL | `references/auto-docs-only.md` (docs-only, via §0c) |
54
- | Anything else (SSH URLs, `git://`, bare hostnames, etc.) | Unclassified | §1 (standard auto-scope) |
69
+ | `kind` | Classification | Route |
70
+ |---|---|---|
71
+ | `docs` | Documentation URL | HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:0`, path `{project_path}`) with its `halt_message` when it gives one, else `references/auto-docs-only.md`, via §0c |
72
+ | `local` | Local filesystem path | §1 |
73
+ | `remote` | Repo URL, cloned from `clone_url` | §1 |
55
74
 
56
- Store the classification result (documentation URL vs. repo/local/other). For all input types, continue to §0b (Pin Resolution).
75
+ Store `{kind}` and `{clone_url}`; every kind continues to §0b.
57
76
 
58
77
  ### 0b. Pin Resolution
59
78
 
60
- This section validates and resolves version pins. It runs for repo URLs and local paths only — skip for documentation URLs (doc URLs have no git repo to pin against). Initialize `{pinned_ref}`, `{pinned_ref_type}`, and `{pinned_version}` as null.
79
+ This section validates and resolves version pins. Initialize `{pinned_ref}`, `{pinned_ref_type}`, and `{pinned_version}` as null.
61
80
 
62
- **For documentation URLs:** Skip this section entirely. Continue to §0c.
81
+ **For documentation URLs** (no git repo to pin against): skip to §0c.
63
82
 
64
- **For local paths when `--pin` is provided:** Emit a warning: "**Local source may not match pinned version {pin_value}.** Ensure you've checked out the correct version locally, or use a remote GitHub URL so SKF can clone from the git tag automatically." Store `{pinned_ref}` = `{pin_value}`, `{pinned_ref_type}` = `"local"`, `{pinned_version}` = `{pin_value}`. Continue to §0c without running `skf-validate-pins.py`.
83
+ **For a local path (`{kind}` is `local`) when `--pin` is provided:** Emit a warning: "**Local source may not match pinned version {pin_value}.** Ensure you've checked out the correct version locally, or use a remote GitHub URL so SKF can clone from the git tag automatically." Store `{pinned_ref}` = `{pin_value}`, `{pinned_ref_type}` = `"local"`, `{pinned_version}` = `{pin_value}`. Continue to §0c without running `skf-validate-pins.py`.
65
84
 
66
- **For repo URLs when `--pin` is provided:**
85
+ **For a repo URL (`{kind}` is `remote`) when `--pin` is provided:**
67
86
 
68
- **Resolve `{validatePinsHelper}`** from `{validatePinsProbeOrder}`; first existing path wins; HALT if neither resolves.
87
+ **Resolve `{validatePinsHelper}`** from `{validatePinsProbeOrder}`; first existing path wins. If neither resolves, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:0b`): "`skf-validate-pins.py` is missing. Re-install SKF."
69
88
 
70
89
  ```bash
71
- uv run {validatePinsHelper} --repo-url {project_path} --pin {pin_value}
90
+ uv run {validatePinsHelper} --repo-url "{clone_url}" --pin {pin_value}
72
91
  ```
73
92
 
74
93
  Handle exit codes:
75
94
 
76
95
  - **Exit 0** (`status: "valid"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Continue to §0c.
77
- - **Exit 1** (`status: "invalid"`): HARD HALT with exit code 3 (`resolution-failure`). Emit error: `"Version pin '{pin_value}' not found in {project_path}. Available matches: {suggestions}. Use a valid tag, branch, or omit --pin for latest."` Emit the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "pin-invalid"`, `mode: "auto"`.
78
- - **Exit 2** (error): HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope with `halt_reason: "resolution-failure"`.
96
+ - **Exit 1** (`status: "invalid"`): HARD HALT (exit code 3, `halt_reason: "pin-invalid"`, phase `step-auto-scope:0b`): "Version pin '{pin_value}' not found in {project_path}. Available matches: {suggestions}. Use a valid tag, branch, or omit --pin for latest."
97
+ - **Exit 2** (error): HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:0b`): "The version pin could not be checked: {the helper's error}."
79
98
 
80
99
  **For repo URLs when `--pin` is not provided (default):**
81
100
 
82
101
  Using the same `{validatePinsHelper}` resolved above:
83
102
 
84
103
  ```bash
85
- uv run {validatePinsHelper} --repo-url {project_path}
104
+ uv run {validatePinsHelper} --repo-url "{clone_url}"
86
105
  ```
87
106
 
88
107
  Handle exit codes:
89
108
 
90
109
  - **Exit 0** (`status: "resolved"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Log: "Default pin resolved: {resolved_ref}". Continue to §0c.
91
110
  - **Exit 1** (no releases found): Set `{pinned_ref}` = null, `{pinned_ref_type}` = null, `{pinned_version}` = null. Log: "No release tags found — using HEAD." Continue to §0c without pinning.
92
- - **Exit 2**: Log warning, continue without pinning (same as exit 1 behavior).
111
+ - **Exit 2**: record the warning with `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning 'pin_unresolved: <the helper message, a single quote written as a backtick>'` (if it fails, go on), then continue to §0c without pinning, as on exit 1.
93
112
 
94
113
  ### 0c. Coexistence Detection
95
114
 
96
- This section checks for existing skills matching the target before proceeding. It runs for all input types (repo URLs, doc URLs, and local paths). Initialize `{coexistence_suffix}` as empty.
115
+ This section checks for existing skills matching the target, whatever its kind, then goes on to its **route**: load, read fully, then execute `references/auto-docs-only.md` for a documentation URL, §1 for every other kind. Initialize `{coexistence_suffix}` as empty.
97
116
 
98
117
  **1. Load skill inventory:**
99
118
 
100
- **Resolve `{skillInventoryHelper}`** from `{skillInventoryProbeOrder}`; first existing path wins; HALT if neither resolves.
101
-
102
- Pass the target (`{project_path}`) so the helper computes the coexistence match set for you — do not re-match by hand:
119
+ Pass the target (`{project_path}`) to `{skillInventoryHelper}`, resolved in §0, so the helper computes the coexistence match set for you; do not re-match by hand:
103
120
 
104
121
  ```bash
105
- uv run {skillInventoryHelper} {skills_output_folder} --match-target {project_path}
122
+ uv run {skillInventoryHelper} "{skills_output_folder}" --match-target "{project_path}"
106
123
  ```
107
124
 
108
- Parse the JSON output. If the exit code is non-zero or the `skills` array is empty, skip coexistence detection silently (no existing skills to conflict with) and continue: load, read fully, then execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types.
125
+ Parse the JSON output. If the exit code is non-zero or the `skills` array is empty, skip coexistence detection silently (no existing skills to conflict with) and go on to the route.
109
126
 
110
127
  **2. Read the match set:**
111
128
 
112
- The helper already performed the match deterministically — scheme / trailing-`.git` / trailing-slash normalization, kebab expected-name derivation (§6 repo/package name, doc hostname per `references/auto-docs-only.md`), and case-insensitive comparison of both the normalized `source_repo` (URL match) and the derived name (name match). Read the top-level **`matches[]`** array from the JSON; do not normalize, derive, or compare anything in the prompt. Each entry is:
129
+ Read the top-level **`matches[]`** array; the helper has already matched, so do not re-match. Each entry is:
113
130
 
114
131
  ```json
115
- { "name": "...", "active_version": "...", "source_repo": "...", "active_path": "...", "match_reason": "url" | "name" | "both" }
132
+ { "name": "...", "active_version": "...", "source_repo": "...", "active_path": "...", "match_reason": "url" | "name" | "both", "skf_skill": true | false }
116
133
  ```
117
134
 
118
- **3. If `matches[]` is empty:**
119
-
120
- Complete silently. Continue: execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. No user output.
121
-
122
- **4. If `matches[]` has one or more entries — coexistence gate:**
123
-
124
- Present the user with the coexistence decision, one bullet per `matches[]` entry (`{skill_name}` = `matches[].name`, `{version}` = `matches[].active_version`, `{source_repo}` = `matches[].source_repo`):
135
+ `skf_skill` is true when SKF generated the matched skill (its `metadata.json` carries an SKF marker). Only those are offered for a merge: a merge hands the skill to update-skill, which rewrites its files, and SKF changes only the skills it generated.
125
136
 
126
- ```
127
- ⚠️ Existing skill(s) found for {target_name}:
128
-
129
- • {skill_name} (v{version}) — source: {source_repo}
130
- [repeat for each entry in matches[]]
131
-
132
- Actions:
133
- [A]longside — Create a new wiki skill with "-wiki" suffix (existing skill untouched)
134
- [M]erge — Update the existing skill via US workflow (wiki data enriches it)
135
- [S]kip — Do not create or modify any skill for this library
136
-
137
- Choose [A/M/S]:
138
- ```
139
-
140
- In headless mode (`{headless_mode}` is true): auto-select `[A]longside` and log: "Headless: coexistence detected for {target_name}, auto-selecting [A]longside"
137
+ **3. If `matches[]` is empty:**
141
138
 
142
- **5. Handle user selection:**
139
+ Complete silently and go on to the route. No user output.
143
140
 
144
- - **[A]longside:** Set `{coexistence_suffix}` to `-wiki`. Continue: execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. The existing skill is untouched.
145
-
146
- - **[M]erge:** If `matches[]` has more than one entry, prompt the user to select which one to merge into before proceeding. Read `{matched_skill_name}` = the selected entry's `matches[].name` and `{matched_active_path}` = its `matches[].active_path`. Emit a redirect envelope signaling the forger to route to the US workflow for the selected skill:
147
- ```
148
- SKF_ANALYZE_RESULT_JSON: {"status":"redirect","redirect_to":"US","skill_name":"{matched_skill_name}","skill_path":"{matched_active_path}","exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"merge"}
149
- ```
150
- Write the result contract per `shared/references/output-contract-schema.md` with `status: "redirect"`.
151
- Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.**
152
-
153
- - **[S]kip:** Emit a skip envelope:
154
- ```
155
- SKF_ANALYZE_RESULT_JSON: {"status":"skipped","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":1,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","coexistence":"skip","skipped_reason":"Existing skill for {matched_skill_name}"}
156
- ```
157
- Write the result contract with `status: "skipped"`.
158
- Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.**
141
+ **4. If `matches[]` has one or more entries:** load, read fully, then execute {coexistenceFile}, the coexistence gate. Its [A]longside comes back here with `{coexistence_suffix}` set to `-wiki` and goes on to the route; its [M]erge and [S]kip end the run through §9.
159
142
 
160
143
  ### 1. Load Context
161
144
 
162
145
  Read {outputFile} frontmatter to obtain:
163
- - `project_paths[]` — the root(s) to analyze
164
- - `forge_tier` — for brief generation
146
+ - `project_paths[]`: the root(s) to analyze
147
+ - `forge_tier`: for brief generation
165
148
  - `project_name`, `user_name`, `date`
149
+ - `scope_hint` (the folders or packages to focus on or skip) and `intent_hint` (the goal of the analysis), each possibly empty: §3b, §5 and §6 apply them
166
150
 
167
151
  Load `references/step-shape-detect.md` as reference for shape detection invocation contract and shape→scope mapping.
168
152
 
169
153
  ### 2. Manifest Scan
170
154
 
171
- Enumerate package manifests **deterministically** via `{scanManifestsHelper}` (the same helper the interactive `scan-project.md` uses) — do not hand-scan. Resolve `{scanManifestsHelper}` as the first path in `{scanManifestsProbeOrder}` that exists. The scanner reads a **local directory**, so how you point it at the target depends on the input form classified in §0:
155
+ Enumerate package manifests **deterministically** via `{scanManifestsHelper}` (the same helper the interactive `scan-project.md` uses): do not hand-scan. Resolve `{scanManifestsHelper}` as the first path in `{scanManifestsProbeOrder}` that exists; if none does, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:2`): "`skf-scan-manifests.py` is missing. Re-install SKF." The scanner reads a **local directory**, so how you point it at the target depends on its `kind`: §0 gave `{kind}` and `{clone_url}` for its target, and §0's `derive-name --target` call gives them for any other path, and its `halt_message` halts the run as in §0, with phase `step-auto-scope:2` and that path as `{project_path}`.
172
156
 
173
157
  **For each path in `project_paths[]`:**
174
158
 
175
- - **Local filesystem path** (starts with `/`, `./`, `~/`, `~`, or is an existing directory) — scan it directly:
159
+ - **`kind` is `local`:** scan it directly. Its **scan root** is the path itself, with a leading `~` written as `$HOME`: the commands below quote every path, and a `~` inside double quotes does not expand.
176
160
 
177
161
  ```bash
178
- uv run {scanManifestsHelper} scan {path}
162
+ uv run {scanManifestsHelper} scan "<the path's scan root>" > "{run_dir}/manifests-{i}.json"
179
163
  ```
180
164
 
181
- - **Remote git URL** (e.g. `github.com/{owner}/{repo}`) — auto-scope has no working tree yet and the scanner cannot read a URL. Fetch **just the manifests** first (blobless + sparse + depth-1 — no source blobs, typically KB–MB even for large monorepos), then scan that tree:
165
+ - **`kind` is `remote`:** auto-scope has no working tree yet and the scanner cannot read a URL. Fetch **just the manifests** into the run folder (blobless + sparse + depth-1: no source blobs, typically KB to MB even for large monorepos); `{run_dir}/clone-{i}`, for the `{i}`-th entry of `project_paths[]` (`clone-1` for §0's target), is the path's **scan root**:
182
166
 
183
167
  ```bash
184
- tmp="$(mktemp -d)"
185
- git clone --filter=blob:none --no-checkout --depth 1 {pinned_branch_flag} {path} "$tmp"
186
- git -C "$tmp" sparse-checkout set --no-cone '**/package.json' '**/Cargo.toml' '**/pyproject.toml' '**/go.mod' '**/pom.xml' '**/build.gradle' '**/build.gradle.kts' '**/Package.swift' 'pnpm-workspace.yaml' '**/pnpm-workspace.yaml'
187
- git -C "$tmp" checkout
188
- uv run {scanManifestsHelper} scan "$tmp"
168
+ git clone --filter=blob:none --no-checkout --depth 1 {pinned_branch_flag} "{clone_url}" "{run_dir}/clone-{i}"
169
+ git -C "{run_dir}/clone-{i}" sparse-checkout set --no-cone '**/package.json' '**/Cargo.toml' '**/pyproject.toml' '**/go.mod' '**/pom.xml' '**/build.gradle' '**/build.gradle.kts' '**/Package.swift' 'pnpm-workspace.yaml' '**/pnpm-workspace.yaml' 'lerna.json' 'rush.json' 'nx.json'
170
+ git -C "{run_dir}/clone-{i}" checkout
171
+ uv run {scanManifestsHelper} scan "{run_dir}/clone-{i}" > "{run_dir}/manifests-{i}.json"
189
172
  ```
190
173
 
191
- where `{pinned_branch_flag}` is `--branch {pinned_ref}` when a pin was resolved in §0b (so manifests match the target version), otherwise omitted. **Retain `"$tmp"` through §3** — shape detection reads the discovered manifest files from it — then it may be discarded.
192
-
193
- Parse the JSON envelope: `{manifests: [{path, ecosystem, ...}], total_unique, monorepo, warnings?}`. The scanner discovers the project root plus monorepo workspace members (npm/pnpm/yarn `workspaces`, Cargo `[workspace]`, and other ecosystems) and sets the `monorepo` flag — so members are found without hand-listing each workspace convention, for both local trees and remote fetches.
174
+ where `{pinned_branch_flag}` is `--branch {pinned_ref}` when a pin was resolved in §0b (so manifests match the target version), otherwise omitted. If a `git` command fails, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:2`, path `{path}`): "{path} could not be fetched: {the first stderr line}." The clone stays in the run folder until the run ends, so §3 and §5 read it in later commands.
194
175
 
195
- From the envelope, record:
176
+ Each `{run_dir}/manifests-{i}.json` holds the scanner's JSON envelope: `{manifests: [{path, ecosystem, ...}], total_unique, monorepo, warnings?}`, each `path` relative to the scan root. The scanner discovers the project root plus monorepo workspace members (npm/pnpm/yarn `workspaces`, Cargo `[workspace]`, and other ecosystems) and sets the `monorepo` flag, so members are found without hand-listing each workspace convention, for both local trees and remote fetches. Shape detection (§3) reads `{run_dir}/manifests-1.json` itself, so no manifest list passes through you. From the envelope, record:
196
177
 
197
- 1. **Supported manifest paths** — filter `manifests[].path` to the types `skf-shape-detect.py` accepts (`package.json`, `pyproject.toml`, `Cargo.toml`, `go.mod`, `pom.xml`, `build.gradle`, `build.gradle.kts`, `Package.swift`). Each `manifests[].path` is **relative to the scan root**, so resolve them against that root (`{path}` for a local scan, `"$tmp"` for a remote fetch) before use. This filtered, comma-joined list of resolved paths is fed to shape detection in §3. For a monorepo, it includes each workspace member's manifest, so the package surface is classified accurately rather than from a bare (and often export-less) repo root. The scanner may discover ecosystems shape detection does not yet classify; those are excluded here, so a repo with no supported manifest falls back to interactive at the next check rather than auto-scoping.
198
- 2. **`monorepo` flag** and the count of discovered supported packages — carried forward as a signal for the decomposition decision in §3a.
178
+ 1. **The `monorepo` flag**, a signal for the decomposition decision in §3a.
179
+ 2. **The names and flags §3b, §6 and the split branch read:** the scan-root manifest's `name` and `private` (the `manifests[]` entry whose `path` has no folder), each member manifest's `name`, `private` and `internal_deps`, and `umbrella_candidates[]`. Read them from the envelope; never open a member manifest for them.
199
180
 
200
- **Harvest tree-level language signals.** A whole-language repo may declare no parser-generator dependency (a hand-written compiler such as rustc, TypeScript, or the Go toolchain) or carry no supported manifest at all (CPython, Ruby). From the **same** fetched tree — no second clone and no blobs, since tree objects are already present in the blobless clone — collect two signals for shape detection. These are pure path listings (`git ls-tree` reads tree objects; no checkout, no blob download):
181
+ **List the target's files once.** Shape detection (§3) and language detection (§5 and the split branch) read the whole file list of the target (`{project_path}`, §0's target) from one run file, `{run_dir}/tree.txt`, so the listing never passes through you or a shell variable. Write it with the first command that fits the target, where `{scan_root}` is the target's scan root:
201
182
 
202
- - **Remote fetch** (`"$tmp"`), or a **local path** that is a git repo (`git -C {path}`):
203
- ```bash
204
- files="$(git -C "$tmp" ls-tree -r --name-only HEAD)" # every file path
205
- dirs="$( git -C "$tmp" ls-tree -r -d --name-only HEAD)" # every directory
206
- # Grammar files (depth-capped to skip deep vendored fixtures, hard-capped):
207
- grammar_matches="$(printf '%s\n' "$files" \
208
- | grep -Ei '\.(g4|pest|lalrpop|y|gram|lark|ebnf|peg|ungram)$|/grammar\.(js|json)$' \
209
- | awk -F/ 'NF<=4' | head -n 50 | paste -sd, -)"
210
- # Directory signals (trailing /) + depth-capped file basenames, narrowed to
211
- # compiler-relevant paths so the argument stays bounded on huge repos. The
212
- # filter is a loose superset of shape detection's gates — the script does the
213
- # precise matching; this only keeps the list small.
214
- tree_paths="$({ printf '%s\n' "$dirs" | sed 's#$#/#'; \
215
- printf '%s\n' "$files" | awk -F/ 'NF<=5'; } \
216
- | grep -Ei '(^|/)(compiler|compile|syntax|scanner|lexer|tokeniz|parser|parse|ast|binder|checker|codegen|ssagen|interpreter|vm|eval|rustc_[a-z]+)' \
217
- | head -n 400 | paste -sd, -)"
218
- ```
219
- - **Local path that is not a git repo** (`{path}`): list the tree with `find` instead, then derive `grammar_matches` / `tree_paths` the same way:
220
- ```bash
221
- files="$(cd {path} && find . -type f -not -path '*/.git/*' | sed 's#^\./##')"
222
- dirs="$( cd {path} && find . -type d -not -path '*/.git/*' | sed 's#^\./##')"
223
- ```
224
-
225
- Record `<grammar_matches>` and `<tree_paths>` (each a comma-joined list, possibly empty) for §3.
183
+ ```bash
184
+ git -C "{scan_root}" -c core.quotePath=false ls-tree -r --name-only HEAD > "{run_dir}/tree.txt"
185
+ git -C "{scan_root}" -c core.quotePath=false ls-files > "{run_dir}/tree.txt"
186
+ (cd "{scan_root}" && find . -type f -not -path '*/.git/*' | sed 's#^\./##') > "{run_dir}/tree.txt"
187
+ ```
226
188
 
227
- **IF no supported manifests are found** (the filtered list is empty):
228
- - **AND** `<grammar_matches>` is empty **AND** `<tree_paths>` shows no compiler directory (none of `compiler/`, `src/compiler/`, `cmd/compile/`, `internal/syntax/`, or a `Parser/`): the repo carries no language signal — emit fallback message: "**Auto-scope could not find any supported package manifests — switching to interactive mode.**" Load, read fully, then execute `references/scan-project.md`. **STOP HERE.**
229
- - **Otherwise** (a grammar file or a compiler directory is present) the repo is a manifest-less language toolchain (CPython, Ruby): proceed to §3 with an **empty** `--manifests` and the harvested `--grammar-files` / `--tree-paths`.
189
+ The first lists a remote fetch (tree objects only: the blobless clone downloads no file for it), the second a local path inside a git work tree (`git -C "{scan_root}" rev-parse --is-inside-work-tree` prints `true`), the third any other local folder. If the command fails or writes an empty file, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:2`, path `{project_path}`): "The files of {project_path} could not be listed: {the first stderr line}."
230
190
 
231
191
  ### 3. Invoke Shape Detection
232
192
 
233
- **Resolve `{shapeDetectHelper}`** from `{shapeDetectProbeOrder}`; first existing path wins; HALT if neither resolves.
193
+ **Resolve `{shapeDetectHelper}`** from `{shapeDetectProbeOrder}`; first existing path wins. If neither resolves, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:3`): "`skf-shape-detect.py` is missing. Re-install SKF."
234
194
 
235
- Invoke the shape detection script with the discovered manifests and the harvested tree-level signals:
195
+ Invoke the shape detection script on the §2 manifest scan and file list. It keeps the manifest types it classifies, resolves each against the scan root and finds the tree-level signals (grammar files, a compiler folder and its lexer, parser and ast) itself, so a manifest-less language toolchain (CPython, Ruby) is recognized from its files:
236
196
 
237
- ```
238
- uv run {shapeDetectHelper} --repo-url <project_path_or_url> \
239
- --manifests <comma_separated_manifest_paths> \
240
- --grammar-files <grammar_matches> --tree-paths <tree_paths>
197
+ ```bash
198
+ uv run {shapeDetectHelper} --repo-url "{project_path}" --manifests-file "{run_dir}/manifests-1.json" --manifest-dir "{scan_root}" --tree-file "{run_dir}/tree.txt"
241
199
  ```
242
200
 
243
- `<comma_separated_manifest_paths>` may be empty for a manifest-less language repo, provided `<grammar_matches>` or `<tree_paths>` carries the signal. Parse the JSON output: `{shape, signals, confidence, export_count, package_count}`
201
+ Parse the JSON output: `{shape, signals, confidence, export_count, package_count}`
244
202
 
245
203
  **Handle exit codes:**
246
204
 
247
205
  - **Exit 0 (shape classified):** Continue to §3a.
248
- - **Exit 1 (unknown shape):** Emit fallback message: "**Auto-scope could not classify this repo — switching to interactive mode.**" Load, read fully, then execute `references/scan-project.md`. **STOP HERE.**
249
- - **Exit 2 (error):** HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "resolution-failure"`, `mode: "auto"`.
206
+ - **Exit 1 (unknown shape):** the run goes on as an interactive analysis. Set `mode: 'interactive'` in {outputFile}'s frontmatter (so a later session resumes it: init archives an unfinished `'auto'` report) and `{auto_mode}` to false. When `{pinned_ref_type}` is `"tag"` or `"branch"`, also set `target_ref: '{pinned_ref}'` and `refs: {each project path: '{pinned_ref}'}` there, so the chain scans every path and writes every brief at the §0b pin (a `"local"` pin, or none, sets neither, as the §8 pin table gives no `target_ref` for them). Then emit the fallback message: "**Auto-scope could not classify this repo: switching to interactive mode.**", then load, read fully, and execute `references/scan-project.md`. **STOP HERE.**
207
+ - **Exit 2 (error):** HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:3`): "Shape detection failed: {the `error` its stderr JSON names}."
250
208
 
251
209
  ### 3a. Check Decomposition Thresholds
252
210
 
@@ -256,131 +214,94 @@ Apply the **Decomposition Thresholds** ladder from `step-shape-detect.md` (loade
256
214
 
257
215
  **Decision:**
258
216
 
259
- - **Threshold not met** (`package_count ≤ 3`) → Continue to §4 (single-scope flow, entirely unchanged).
217
+ - **Threshold not met** (`package_count ≤ 3`) → Continue to §4 (single-scope flow).
260
218
  - **Threshold met** (`package_count > 3`) → this repo is a **decomposition candidate**. A threshold firing means the repo *could* decompose, not that it *should* — continue to §3b to decide merge-vs-split. Log: "Auto-decomposition candidate: package_threshold ({value} packages exceeds 3)".
261
219
 
262
220
  ### 3b. Cohesion Check — Merge to One Skill vs Split into N
263
221
 
264
- Reached only when §3a flagged a decomposition candidate. Most published monorepos are **cohesive** and produce a better single skill than a pile of fragments — empirically, 5/5 real monorepos (animato 15 crates, trpc, react 38 packages, aws-sdk-js-v3 442 packages, plus zod) were best served as one cohesive skill or a curated few, not one-skill-per-package. Decide deliberately:
222
+ Reached only when §3a flagged a decomposition candidate. Most published monorepos are **cohesive** and produce a better single skill than a pile of fragments. Load {heuristicsFile} and apply its **Cohesion Triggers**, the one statement of when members belong in one skill, reading their evidence from the §2 envelope (`umbrella_candidates[]`, and each member's `name`, `private` and `internal_deps`) and never from the member manifests themselves. Decide deliberately:
265
223
 
266
- **Merge into ONE cohesive skill** (override the threshold → continue to §4 single-scope) when **any** of these hold:
224
+ - **Merge into ONE cohesive skill** (override the threshold → continue to §4 single-scope) when **any** cohesion trigger holds.
225
+ - **Split into N skills** when the members meet its split condition instead: load, read fully, then execute {splitFile}, which replaces §4 to §6 and returns to §7.
267
226
 
268
- - **Umbrella facade** — one package re-exports the members: a root or named package whose dependencies include the other workspace members, or which `pub use` / `export *`s them. The facade *is* the public surface (e.g. animato's `crates/animato` re-exporting its 15 sub-crates).
269
- - **Shared runtime contract** — the members are consumed together through one entry point, and teaching the shared invariant covers them (e.g. tRPC's adapters around `@trpc/server`; aws-sdk's `new XClient(...) → client.send(new YCommand(...))` shared by every `@aws-sdk/client-*`).
270
- - **Internal building blocks** — the members are private/internal pieces of one product, not independently meaningful to a consumer.
227
+ If genuinely unsure, **prefer merge** — a too-broad single skill is recoverable with `US`; N fragmented skills are not.
271
228
 
272
- **Split into N skills** (→ §4a) when:
229
+ **Record the decision** the moment you make it, so the run's result and a later halt both carry it: write `{run_dir}/decision.json` as `{"gate": "auto-scope.cohesion", "default_action": "merge", "taken_action": "<merge or split>", "reason": "<the cohesion trigger that held, or why the members split>", "evidence": {"package_count": <package_count>, "members": [<the §2 member names>]}}` and run
273
230
 
274
- - The members are **independently published with distinct public surfaces serving different concerns**, **and no umbrella re-exports them** — e.g. `react-dom` and `react-server-dom-*` are separate installs with separate jobs, or a federated SDK where a consumer only ever wants one service. Each genuinely-distinct facet earns its own skill.
231
+ ```bash
232
+ uv run {emitEnvelopeHelper} record --workflow skf-analyze-source --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"
233
+ ```
275
234
 
276
- If genuinely unsure, **prefer merge** — a too-broad single skill is recoverable with `US`; N fragmented skills are not.
235
+ If the command fails, go on: only that entry is lost.
277
236
 
278
237
  **Facet-coverage guard (merged facet-diverse repos only).** When you merge a repo whose members have genuinely distinct surfaces and you scope to only some of them, record the decision explicitly — never drop a facet silently:
279
238
 
280
- - In `scope.notes`, name the in-scope facets **and** the excluded major facets, e.g. _"Scoped to react + react-dom core; excludes react-server-dom-\* (RSC), the specialized renderers (react-art/native/test), and the compiler — forge a separate skill for those."_
239
+ - When `intent_hint` is not empty, the facets it names are the ones in scope.
240
+ - In `scope.notes`, name the in-scope facets **and** the excluded major facets, e.g. _"Scoped to react + react-dom core; excludes react-server-dom-\* (RSC), the specialized renderers (react-art/native/test), and the compiler: forge a separate skill for those."_
281
241
  - Surface the excluded facets in the analysis report (§7) so the operator can re-scope or forge a companion skill.
282
242
 
283
243
  ### 4. Map Shape to Scope
284
244
 
285
- Apply the canonical **Shape → Scope Type Mapping** table from `step-shape-detect.md` (loaded at §1) — the single source of truth for this ladder (the `export_count > 200 → public-api` split, the `language-reference` corpora caveat, and the `stack-compose` decomposition note).
245
+ Apply the canonical **Shape → Scope Type Mapping** table from `step-shape-detect.md` (loaded at §1).
286
246
 
287
- ### 5. Generate Include/Exclude Patterns
247
+ **An app or a library on a framework.** When §3's `signals` hold `app_or_library:framework_dep`, take that table's row for it and judge which of its two cases the repository is from the scan-root manifest's `description` (its `manifests[]` entry in `{run_dir}/manifests-1.json`), or, when that does not settle it, the README's opening paragraph (a remote fetch checked out only the manifests: `git -C "{scan_root}" sparse-checkout add '/README*'` brings the README in). From here on `{shape}` is the shape you chose. Record the choice: write `{run_dir}/decision.json` as `{"gate": "auto-scope.shape", "default_action": "reference-app", "taken_action": "<reference-app or library-API>", "reason": "<the sentence of the description or README that decided>", "evidence": {"signals": [<the §3 signals>]}}` and run `uv run {emitEnvelopeHelper} record --workflow skf-analyze-source --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"` (if it fails, go on).
288
248
 
289
- Generate `scope.include` and `scope.exclude` arrays from the detected language and project structure.
249
+ **A language on a parser.** When §3's `signals` hold `language_or_user:parser_dep`, judge which case of that signal's row the repository is, from the same description or README: from here on `{shape}` is the shape that case gives. Record it the same way (after the choice above when both hold), with `default_action` the shape you judged from and `taken_action` the new `{shape}`.
290
250
 
291
- **Detect the primary language once, deterministically**, via the shared helper — the single source of truth for the manifest→language rule table (§6b resolves the same helper; do not restate the table in prose, where it drifts from the script). **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}` (first existing path wins). Pipe the §2 supported-manifest paths (and, for a manifest-less toolchain, the harvested `<tree_paths>`) as the file tree:
251
+ ### 5. Generate Include/Exclude Patterns
252
+
253
+ **Detect the primary language once**, from the §2 file list, with the shared helper. **Resolve `{detectWorkspacesHelper}`** from `{detectWorkspacesProbeOrder}` and **`{detectLanguageHelper}`** from `{detectLanguageProbeOrder}`; first existing path wins for each. If one has no candidate, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:5`): "`{the missing script}` is missing. Re-install SKF." First find the workspace layout, which reads the root manifests from the scan root (§2):
292
254
 
293
255
  ```bash
294
- echo '{"tree": [<§2 supported manifest paths + harvested tree paths>]}' | uv run {detectLanguageHelper}
256
+ uv run {detectWorkspacesHelper} --tree-file "{run_dir}/tree.txt" --manifest-dir "{scan_root}"
295
257
  ```
296
258
 
297
- Read `.language` as `{detected_language}` (and `.confidence`) — the helper owns the `tsconfig.json` JS-vs-TS and `build.gradle` Java-vs-Kotlin disambiguation. §6 reuses `{detected_language}` for the brief's `language` field: detect once.
298
-
299
- **Default patterns (adjust based on actual project structure):**
300
-
301
- | Language | Default include | Default exclude |
302
- |----------|-----------------|-----------------|
303
- | TypeScript/JavaScript | `['src/**/*.ts', 'src/**/*.tsx']` | `['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']` |
304
- | Python | `['src/**/*.py']` or `['{package_name}/**/*.py']` | `['**/*_test.py', '**/test_*.py', '**/tests/**']` |
305
- | Rust | `['src/**/*.rs']` | `['**/tests/**', '**/benches/**']` |
306
- | Go | `['**/*.go']` | `['**/*_test.go', '**/vendor/**']` |
307
- | Java | `['src/main/java/**/*.java']` | `['**/src/test/**']` |
308
- | Kotlin | `['src/main/kotlin/**/*.kt']` | `['**/src/test/**']` |
309
- | Swift | `['Sources/**/*.swift']` | `['**/Tests/**']` |
310
-
311
- **Adjust for actual layout:** If the project uses a non-standard layout (e.g., `lib/` instead of `src/`, or a named package directory for Python), detect and use the actual paths. Check for the existence of common source directories (`src/`, `lib/`, `pkg/`, the package name directory) and prefer the one that exists.
259
+ `{workspace_kind}` ← its `manifest_kind` (null when it exits non-zero). Then detect the language, passing `--workspace-signal {workspace_kind}` only when `{workspace_kind}` is not null, so a Cargo or Python workspace root wins over a nested package.json:
312
260
 
313
- ### 6. Build Scope and Determine Skill Name
314
-
315
- Build the scope object:
316
- ```yaml
317
- scope:
318
- type: '{mapped_scope_type}'
319
- include: ['{generated_include_patterns}']
320
- exclude: ['{generated_exclude_patterns}']
321
- notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}'
261
+ ```bash
262
+ uv run {detectLanguageHelper} --tree-file "{run_dir}/tree.txt" [--workspace-signal {workspace_kind}]
322
263
  ```
323
264
 
324
- Determine the skill name from the project name or package name (kebab-case, lowercase). Use the manifest `name` field if available, otherwise derive from the project directory name. If `{coexistence_suffix}` is non-empty, append it to the skill name.
325
-
326
- For the brief's `language` field, **reuse `{detected_language}` from §5** — do not re-detect (the §5 helper already resolved js-vs-ts from `tsconfig.json` and Java-vs-Kotlin from the tree).
327
-
328
- ### 6b. Seed Companion Corpora (whole-language references only)
329
-
330
- Runs only when §3 classified the repo as `language-reference` **via a whole-language signal** — the `signals` array contains a `grammar_file:` or `tree_triad:` entry (a compiler / interpreter / grammar repo such as rust-lang/rust, TypeScript, CPython). **Skip** when `language-reference` fired only from `parser_producer:` / `parser_dep:` signals (a parser *library* such as pest or lalrpop): there the code **is** the product, so no companion prose is needed and the §6/§7 caveat below does not apply.
331
-
332
- A whole-language skill's value is in the language's **prose** — the guide/Book, the standard/library API docs, idioms — not the compiler internals. Seed those canonical corpora so the forged skill teaches the language rather than its implementation.
333
-
334
- **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}` and **`{languageCorporaHelper}`** from `{languageCorporaProbeOrder}` (first existing path wins).
335
-
336
- 1. **Derive the corpus language key `{corpus_language}`.** Prefer `{detected_language}` (from §5) when non-empty. Otherwise — a manifest-less toolchain such as CPython or Ruby — resolve it from the file paths harvested in §2:
337
- ```bash
338
- echo '{"tree": [<harvested §2 file paths>]}' | uv run {detectLanguageHelper}
339
- ```
340
- Use its `language` field. (`.c`/`.h`/`.y` are not in the detector's extension map, so a C-hosted language resolves by its real sources — Ruby via `.rb`.)
341
- 2. **Look up canonical corpora:**
342
- ```bash
343
- uv run {languageCorporaHelper} --language {corpus_language}
344
- ```
345
- - exit 0 → parse the `[{url, label, source}]` array (each seed carries `source: language-registry`) → these are `{corpus_seeds}`.
346
- - exit 1 → no registry entry (long-tail language) → `{corpus_seeds}` is empty (README detection in brief-skill remains the only source).
347
- - exit 2 → log a warning and treat as empty (best-effort; never halt).
348
- 3. Record `{N}` = number of seeds and `{corpus_labels}` = comma-joined labels, carried into the brief `doc_urls` (§8) and the honest caveat (§6/§7).
349
- 4. Build `{corpus_caveat}` (appended to `scope.notes` in §6/§8 and surfaced in §7) so the operator knows a code-only whole-language skill is low-value:
350
- - `{N}` ≥ 1: `" LANGUAGE-REFERENCE CAVEAT: this skill's value is the {corpus_language} prose (guide/Book + std/library docs), not compiler internals. Seeded {N} corpus URL(s): {corpus_labels}. create-skill foregrounds this registry prose as the skill's Language Guide and demotes compiler-internal signatures to a reference-only section — review the forged skill if compiler internals still dominate."`
351
- - `{N}` == 0: `" LANGUAGE-REFERENCE CAVEAT: no canonical corpora were found for {corpus_language} (README detection and the registry both came up empty). This skill is LOW-VALUE as code-only — attach the {corpus_language} guide + std/library docs manually (re-run with a doc URL, or enrich via US) before forging."`
352
-
353
- For a parser-library `language-reference` (skipped above) and every other shape, `{corpus_caveat}` is empty.
265
+ The helper owns the `tsconfig.json` JS-vs-TS and `build.gradle` Java-vs-Kotlin disambiguation, ranks the manifests nearest the root first, and never lets a manifest in a docs, example, test or tool folder decide while another one exists. Read `{detected_language}`:
354
266
 
355
- ### 4a. Multi-Scope Decomposition
267
+ - **`detected_languages` has one entry or none, or `.confidence` is `high` and the §2 scan-root manifests (the `manifests[]` entries whose `path` has no folder) are all of one ecosystem:** `{detected_language}` ← `.language`. A manifest at the root decided, so the choice below would give the same answer.
268
+ - **Otherwise** (several languages, and no single-ecosystem root manifest decided): `.language` is the helper's first guess, not the answer. Choose the language the skill documents: among `detected_languages`, the language of the scan-root manifest (the §2 `manifests[]` entry whose `path` has no folder; with several, the one whose `name` is the package this repository publishes), or, when there is none, `.source_language`, the language most of the files are written in (CPython's listing holds only manifests below its root, so `.confidence` is `medium` and its sources say `python`). Record the choice: write `{run_dir}/decision.json` as `{"gate": "auto-scope.language", "default_action": "<detected_languages[0]>", "taken_action": "<the language you chose>", "reason": "<the manifest or the source files that decided>", "evidence": {"detected_languages": [<detected_languages>], "source_language": "<source_language>"}}` and run `uv run {emitEnvelopeHelper} record --workflow skf-analyze-source --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"` (if it fails, go on).
356
269
 
357
- This section is reached only from §3b when the cohesion check decided to **split** a monorepo (members are independently published with distinct surfaces and no umbrella re-exports them). It replaces §4→§5→§6 for repos that will produce N > 1 skills.
270
+ **Take the patterns from the §2 listing**, never from the folders on disk (a remote scan root holds only the manifests): `scope.include` and `scope.exclude` are what this prints:
358
271
 
359
- **Decompose by workspace package:** Use workspace package discovery from §2 manifest scan results. Each workspace package with its own manifest becomes a separate skill boundary. Name each skill as `{project_name}-{package_name}` (kebab-case); if `{coexistence_suffix}` is non-empty, append it. Trivial workspace members (no source files, no exports) are excluded.
272
+ ```bash
273
+ uv run {detectLanguageHelper} scope-patterns --tree-file "{run_dir}/tree.txt" --language {detected_language}
274
+ ```
360
275
 
361
- **Per-boundary shape→scope mapping:**
276
+ **Apply `scope_hint`** when it is not empty: root the include patterns at the folders or packages it focuses on (`packages/auth/**/*.ts`), and add `<folder>/**` to the exclude patterns for each folder it skips. Name the hint in `scope.notes`.
362
277
 
363
- For each decomposed boundary, apply the shape→scope mapping from §4 independently — re-run the shape→scope heuristic ladder from `step-shape-detect.md` per package using each package's own manifest data. Packages may have different shapes (e.g., a `library-API` core + a `reference-app` CLI).
278
+ ### 6. Build Scope and Determine Skill Name
364
279
 
365
- ### 5a. Generate Multi-Scope Patterns
280
+ The single-scope brief's scope (§8 hands these values to the brief writer):
366
281
 
367
- For each decomposed boundary, generate include/exclude patterns using the same language-aware rules as §5, but scoped to the boundary's source paths. Monorepo boundaries are rooted at the package path (e.g., `packages/auth/src/**/*.ts` instead of `src/**/*.ts`).
282
+ - `scope.type`: the §4 mapped scope type
283
+ - `scope.include` and `scope.exclude`: the §5 patterns
284
+ - `scope.notes`: `Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}`, plus the `scope_hint` note of §5 when there is one
368
285
 
369
- ### 6a. Build Multi-Scope
286
+ **Whole-language reference:** when §3 classified the repo as `language-reference` and its `signals` hold a `grammar_file:` or `tree_triad:` entry, load, read fully, then execute {corporaFile} now: it sets `{corpus_seeds}`, `{corpus_caveat}`, `{corpus_doc_urls}` and the report's Companion Corpora subsection, then returns here. Otherwise (every other shape, a parser library included) `{corpus_caveat}` is empty and `{corpus_doc_urls}` null.
370
287
 
371
- For each boundary, build a scope object following the same structure as §6.
288
+ Name the skill with the helper that names every brief, by {heuristicsFile}'s Unit Names, so the name the brief gets is the name the coexistence check looks up:
372
289
 
373
- Include decomposition metadata in `scope.notes`: "Decomposed from {project_name} — boundary {i}/{N} ({reason})"
290
+ ```bash
291
+ uv run {skillInventoryHelper} derive-name --from - --skills-folder "{skills_output_folder}" <<'SKF_SKILL_NAME'
292
+ [{"target": "{project_path}", "manifest_name": <name>, "private": <private>, "members": [<member names>]}]
293
+ SKF_SKILL_NAME
294
+ ```
374
295
 
375
- Determine each boundary's skill name from the boundary-derived name (kebab-case, lowercase). If `{coexistence_suffix}` is non-empty, append it to each skill name. Detect the primary language from each boundary's manifest ecosystem (same rules as §6).
296
+ `manifest_name` is the umbrella candidate's `name` when §3b merged on the umbrella facade trigger, else the §2 scan-root manifest's `name` (null without one), and `private` that manifest's `private`. `members` lists the `name` of every §2 member manifest whose `private` is not `true` (`[]` for a single package): a merged monorepo whose root names no published package takes the name they share (`aws-sdk` for the `@aws-sdk/*` packages), else the repository's. `{skill_name}` ← `names[0].name`; when the entry is in `unnamed` (a target with no folder name), name the skill after the analyzed folder yourself. A split names its boundaries in one call the same way, and names the entries `unnamed` and `duplicates` list from their folders.
376
297
 
377
- **Pin data (from §0b):** All N decomposed briefs share the same pin — the pin targets a repo-level ref, not a package-level version. Apply the same `target_version`/`target_ref` values from §0b to all N boundaries at brief write time (§8).
298
+ When `names[0].existing` is not null and names a skill §0c did not present (a skill from another source that already has the name this brief gets), load, read fully, then execute {coexistenceFile} with it as the one `matches[]` entry before §7 writes anything: [M]erge and [S]kip end the run there; [A]longside, the headless choice, sets `{coexistence_suffix}` to `-wiki` and continues here. Then, if `{coexistence_suffix}` is non-empty, append it to the skill name.
378
299
 
379
- After building all N scopes, continue to §7 with the full set of boundaries.
300
+ The brief's `description` (one to three sentences, from the shape, the language and the manifest name) is worded toward `intent_hint` when it is not empty.
380
301
 
381
302
  ### 7. Write Analysis Report
382
303
 
383
- Update {outputFile} with auto-scope results. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`.
304
+ Update {outputFile} with auto-scope results. If the write fails, HARD HALT (exit code 4, `halt_reason: "write-failed"`, phase `step-auto-scope:7`, path `{outputFile}`): "The analysis report could not be written: {the error}."
384
305
 
385
306
  **Update frontmatter:**
386
307
  ```yaml
@@ -408,11 +329,11 @@ decomposition:
408
329
 
409
330
  Each `confirmed_units` entry includes `boundary_path` — the relative path to the boundary's root (e.g., `packages/core`). Omit the `decomposition` key entirely when single-scope (N = 1).
410
331
 
411
- **When single-scope (N = 1):** No `decomposition` key. `confirmed_units` contains a single entry (existing behavior).
332
+ **When single-scope (N = 1):** No `decomposition` key. `confirmed_units` contains a single entry.
412
333
 
413
334
  **Append body section:**
414
335
 
415
- For single-scope (unchanged):
336
+ For single-scope:
416
337
  ```markdown
417
338
  ## Auto-Scope Analysis
418
339
 
@@ -426,114 +347,108 @@ For single-scope (unchanged):
426
347
  **Exclude Patterns:** {exclude patterns}
427
348
  ```
428
349
 
429
- **When the shape is a whole-language `language-reference`** (§6b ran — a `grammar_file:`/`tree_triad:` signal), append a Companion Corpora subsection so the operator sees whether the skill has the prose that makes it useful. The status is computed from the **final** brief `doc_urls` (the entries that will actually be fetched), not the seed count alone:
350
+ **When {corporaFile} ran** (a whole-language `language-reference`), append the Companion Corpora subsection it gives.
430
351
 
431
- ```markdown
432
- ## Companion Corpora (language-reference)
352
+ **For a split (N > 1):** append the decomposition body {splitFile} gives instead of the single-scope section.
433
353
 
434
- **Why:** A whole-language skill's value is its prose (guide/Book, std/library docs, idioms), not compiler internals.
435
- **Corpora in brief doc_urls:** {final_doc_urls_count}
436
- - {label}: {url} # one line per doc_urls entry
437
- **Status:** {ATTACHED — canonical corpora present | DEGRADED — code-only, no canonical corpora; attach the {corpus_language} guide + std/library docs before forging}
438
- ```
439
-
440
- For multi-scope (N > 1):
441
- ```markdown
442
- ## Auto-Scope Analysis — Decomposition ({N} skills)
354
+ ### 8. Write Skill Briefs
443
355
 
444
- **Mode:** auto
445
- **Decomposition:** {reason} ({N} boundaries)
446
- **Parent Shape:** {shape} (confidence: {confidence})
447
- **Export Count:** {export_count}
448
- **Package Count:** {package_count}
356
+ The brief writer renders, checks and writes every brief, and applies the version precedence (`target_version`, then `detected_version`, then `1.0.0`) and the rule that `target_version` equals `version`. This section supplies values and never writes YAML. **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}` and **`{validateBriefSchemaHelper}`** from `{validateBriefSchemaProbeOrder}`; first existing path wins for each. If one has no candidate, HARD HALT (exit code 3, `halt_reason: "resolution-failure"`, phase `step-auto-scope:8`): "`{the missing script}` is missing. Re-install SKF."
449
357
 
450
- ### Boundary 1: {boundary_name}
451
- **Scope Type:** {scope_type}
452
- **Boundary Path:** {boundary_path}
453
- **Include Patterns:** {include patterns}
454
- **Exclude Patterns:** {exclude patterns}
455
- **Rationale:** {boundary_rationale}
358
+ **For each confirmed unit** (1 for single-scope, N for decomposition), write its context as `{run_dir}/brief-{skill_name}.json`, a file and never an echo'd string (the language caveat holds an apostrophe):
456
359
 
457
- ### Boundary 2: {boundary_name}
458
- ...
360
+ ```json
361
+ {
362
+ "name": "{skill_name}",
363
+ "target_version": "{target_version from the pin table, or null}",
364
+ "target_ref": "{target_ref from the pin table, or null}",
365
+ "detected_version": "{detected_version, or null}",
366
+ "source_type": "source",
367
+ "source_repo": "{project_path}",
368
+ "language": "{detected_language}",
369
+ "description": "{1-3 sentence description based on shape, language, and manifest name}",
370
+ "forge_tier": "{forge_tier}",
371
+ "created": "{current_date}",
372
+ "created_by": "{user_name}",
373
+ "scope_type": "{scope_type}",
374
+ "scope_include": ["{include_patterns}"],
375
+ "scope_exclude": ["{exclude_patterns}"],
376
+ "scope_notes": "{scope.notes}",
377
+ "scope_rationale": null,
378
+ "scope_tier_a_include": null,
379
+ "scope_amendments": null,
380
+ "scope_registry_path": null,
381
+ "scope_ui_variants": null,
382
+ "scope_demo_patterns": null,
383
+ "doc_urls": null,
384
+ "scripts_intent": null,
385
+ "assets_intent": null,
386
+ "source_authority": null,
387
+ "source_ref": null
388
+ }
459
389
  ```
460
390
 
461
- ### 8. Write Skill Brief
391
+ Only `description` and `scope_notes` are in your words. Every other value comes from an earlier section:
462
392
 
463
- **For each confirmed unit** (1 for single-scope, N for decomposition):
393
+ - `name`, `scope_type`, `scope_include`, `scope_exclude` and `scope_notes`: §6 for a single scope; the split branch's §6a for each boundary of a decomposition.
394
+ - `language`: §5's `{detected_language}` for a single scope (detect once); the split branch's §6a for each boundary.
395
+ - `detected_version`: by `assets/skill-brief-schema.md`'s **Version Detection**, for the §2 scan-root manifest (its `manifests[]` entry in `{run_dir}/manifests-1.json`).
396
+ - `target_version` and `target_ref`, from the §0b pin, the same for every brief:
464
397
 
465
- Create directory `{forge_data_folder}/{skill_name}/` if it does not exist. If a brief write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`.
398
+ | `{pinned_ref_type}` | `target_version` | `target_ref` |
399
+ |---|---|---|
400
+ | `"tag"` | `{pinned_version}` | `{pinned_ref}` |
401
+ | `"branch"` | null | `{pinned_ref}` |
402
+ | `"local"` | `{pinned_version}` when it is a version (`X.Y.Z`, with an optional leading `v`), else null | null |
403
+ | null (no pin, no release) | null | null |
466
404
 
467
- Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` conforming to the skill-brief schema (`{briefSchemaPath}`):
405
+ - `doc_urls`: `{corpus_doc_urls}` (null unless {corporaFile} seeded corpora).
468
406
 
469
- ```yaml
470
- name: '{skill_name}'
471
- version: '{detected_version or 1.0.0}'
472
- source_repo: '{project_path}'
473
- language: '{detected_language}'
474
- scope:
475
- type: '{scope_type}'
476
- include:
477
- - '{include_patterns}'
478
- exclude:
479
- - '{exclude_patterns}'
480
- notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}'
481
- description: '{1-3 sentence description based on shape, language, and manifest name}'
482
- forge_tier: '{forge_tier}'
483
- created: '{current_date}'
484
- created_by: '{user_name}'
485
- ```
407
+ **Gate every brief before any is written.** For each one, render it into the run folder and run on it the schema check skf-brief-skill runs when it reads a brief:
486
408
 
487
- **Companion corpora (whole-language references).** When §6b produced `{corpus_seeds}` (`{N}` ≥ 1), add them as the brief's `doc_urls` so the language's prose is fetched and assembled alongside the code:
488
-
489
- ```yaml
490
- doc_urls:
491
- - { url: '{seed.url}', label: '{seed.label}', source: '{seed.source}' } # one entry per §6b seed; source is 'language-registry'
409
+ ```bash
410
+ uv run {writeSkillBriefHelper} write --target "{run_dir}/briefs/{skill_name}/skill-brief.yaml" --from-flat < "{run_dir}/brief-{skill_name}.json"
411
+ uv run {validateBriefSchemaHelper} "{run_dir}/briefs/{skill_name}/skill-brief.yaml"
492
412
  ```
493
413
 
494
- These are the brief's *existing* `doc_urls`; brief-skill's README detection then merges additional discovered docs on top (existing entries win). **When `{N}` is 0, omit the `doc_urls` key entirely** — the schema requires at least one entry when the key is present.
495
-
496
- **When decomposition is active (N > 1 units):**
497
-
498
- Loop over all N boundaries. For each boundary:
499
- - `name` is the boundary-derived skill name (e.g., `my-monorepo-core`)
500
- - `include`/`exclude` patterns are boundary-scoped (from §5a)
501
- - `scope.notes` includes decomposition context: "Decomposed from {project_name} ({N} skills) — boundary {i}/{N}: {boundary_description}"
502
- - `description` references the parent project and boundary role (e.g., "Core library package of the my-monorepo project, providing...")
503
- - All N briefs share the same `version`, `source_repo`, `language`, `forge_tier`, `created`, `created_by` values as the parent project
414
+ When the writer exits non-zero or the validator answers `valid: false`, the brief is rejected: HARD HALT (exit code 4, `halt_reason: "write-failed"`, phase `step-auto-scope:8`, path `{forge_data_folder}/{skill_name}/skill-brief.yaml`): "The brief for {skill_name} was rejected: {the writer's `message` and `field`, or each `errors[].message` of the validator}." No brief reaches `{forge_data_folder}` then, so the run never reports a brief that does not validate.
504
415
 
505
- **Version detection:** Attempt to auto-detect the source version per the version detection rules in `{briefSchemaPath}`. Fall back to `1.0.0` if detection fails.
416
+ **Then write each brief** to its place, from the same context (the writer renders the same bytes, creates the folder and writes atomically):
506
417
 
507
- **Pin data (from §0b):** When `{pinned_ref}` is non-null, enrich the brief with pin data:
508
-
509
- - If `{pinned_ref_type}` is `"tag"`: set `target_version` = `{pinned_version}`, `target_ref` = `{pinned_ref}`, `version` = `{pinned_version}`.
510
- - If `{pinned_ref_type}` is `"branch"`: set `target_ref` = `{pinned_ref}`, leave `target_version` = null, `version` = auto-detected or `1.0.0`.
511
- - If `{pinned_ref_type}` is `"local"`: set `target_version` = `{pinned_version}`, `target_ref` = null, `version` = `{pinned_version}`.
512
-
513
- When `{pinned_ref}` is null (no pin, no releases): leave `target_version` = null, `target_ref` = null — existing version detection applies unchanged.
418
+ ```bash
419
+ uv run {writeSkillBriefHelper} write --target "{forge_data_folder}/{skill_name}/skill-brief.yaml" --from-flat < "{run_dir}/brief-{skill_name}.json"
420
+ ```
514
421
 
515
- In the docs-only path (`references/auto-docs-only.md`), `--pin` is ignored (already skipped at §0b). No changes to that path.
422
+ Keep each `brief_path` it prints for §9. If it exits non-zero, HARD HALT (exit code 4, `halt_reason: "write-failed"`, phase `step-auto-scope:8`, path `{forge_data_folder}/{skill_name}/skill-brief.yaml`, with `unit_counts` and the `brief_paths` written so far): "The brief for {skill_name} could not be written: {its `message`}."
516
423
 
517
- ### 9. Emit Result Envelope
424
+ ### 9. End the Run
518
425
 
519
- Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stdout:
426
+ The shared emitter writes the result files and prints the envelope; this section stages their content, in every mode. After §8, write `{run_dir}/result-context.json` (a redirect or a skip from §0c has already written its own and comes here directly):
520
427
 
521
- ```
522
- SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path_1}","{brief_path_2}",...,"{brief_path_N}"],"unit_counts":{"confirmed":N,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto"}
428
+ ```json
429
+ {
430
+ "status": "success",
431
+ "report_path": "{outputFile as an absolute path}",
432
+ "brief_paths": ["{the brief_path of each brief §8 wrote}"],
433
+ "unit_counts": {"confirmed": <N>, "skipped": 0, "maybe": 0},
434
+ "mode": "auto",
435
+ "result_contract": {
436
+ "skill": "skf-analyze-source",
437
+ "status": "success",
438
+ "outputs": [{"type": "report", "path": "{outputFile as an absolute path}"}, {"type": "brief", "path": "{each brief_path}"}],
439
+ "summary": {"mode": "auto", "shape": "{shape}", "brief_count": <N>, "units": ["{each skill_name}"]}
440
+ }
441
+ }
523
442
  ```
524
443
 
525
- `brief_paths` contains N paths (one per confirmed unit). `unit_counts.confirmed` is N.
444
+ Add `"coexistence": "alongside"` when `{coexistence_suffix}` is non-empty ([A]longside was selected in §0c), `"pinned_ref": "{pinned_ref}"` when `{pinned_ref}` is non-null, and `"pinned_version": "{pinned_version}"` when `{pinned_version}` is non-null (a branch pin, or a latest release whose tag is not a version, has none): these flow downstream to BS/CS for provenance recording. Put the same pin fields in `summary`. Then run:
526
445
 
527
- If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
528
-
529
- When `{pinned_ref}` is non-null, include `"pinned_ref":"{pinned_ref}"` and `"pinned_version":"{pinned_version}"` in the envelope. These flow downstream to BS/CS for provenance recording. When `{pinned_ref}` is null, omit these fields.
530
-
531
- ### 10. Write Result Contract
532
-
533
- Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{forge_data_folder}/analyze-source-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{forge_data_folder}/analyze-source-result-latest.json`. `outputs` lists all N brief paths and `summary` includes the brief count N. If the per-run record cannot be written, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`.
446
+ ```bash
447
+ uv run {emitEnvelopeHelper} emit --workflow skf-analyze-source --run-dir "{run_dir}" --result-dir "{forge_data_folder}" < "{run_dir}/result-context.json"
448
+ ```
534
449
 
535
- If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
450
+ The emitter stamps the UTC time, the run id and the decisions and warnings the run recorded into the record, writes `{forge_data_folder}/analyze-source-result-{YYYYMMDD-HHmmss}.json` and its `analyze-source-result-latest.json` copy, and prints the `SKF_ANALYZE_RESULT_JSON:` line on stdout (field rules in `references/headless-contract.md`). When `{headless_mode}` is true, display the line verbatim. A result file that could not be written leaves the line's `result_path` null and a `result_file_write_failed` warning in it, and the run still finishes. If the emitter exits non-zero, correct `result-context.json` from the message on its stderr and run it once more; if it fails again, HARD HALT (exit code 4, `halt_reason: "write-failed"`, phase `step-auto-scope:9`, path `{forge_data_folder}`): "The result contract could not be written: {its message}."
536
451
 
537
- ### 11. Chain to Health Check
452
+ If `{onCompleteCommand}` is non-empty and the line's `result_path` is not null, invoke it now: `{onCompleteCommand} --result-path={forge_data_folder}/analyze-source-result-latest.json`. Display a hook failure; it never fails the run.
538
453
 
539
- Load, read fully, then execute {nextStepFile} to run the shared workflow health check.
454
+ Then delete the run folder, whose payloads the emitter has read: `rm -rf "{run_dir}"`. Load, read fully, then execute {nextStepFile} to run the shared workflow health check.