bmad-module-skill-forge 1.9.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/README.md +10 -5
  4. package/docs/_data/pinned.yaml +2 -2
  5. package/docs/agents.md +11 -2
  6. package/docs/architecture.md +5 -4
  7. package/docs/bmad-synergy.md +30 -7
  8. package/docs/campaign.md +172 -0
  9. package/docs/examples.md +7 -3
  10. package/docs/forge-auto.md +90 -0
  11. package/docs/getting-started.md +9 -0
  12. package/docs/how-it-works.md +6 -4
  13. package/docs/index.md +4 -3
  14. package/docs/skill-model.md +1 -1
  15. package/docs/troubleshooting.md +17 -1
  16. package/docs/verifying-a-skill.md +2 -2
  17. package/docs/why-skf.md +1 -1
  18. package/docs/workflows.md +64 -24
  19. package/package.json +2 -2
  20. package/src/knowledge/skill-lifecycle.md +1 -1
  21. package/src/module-help.csv +4 -3
  22. package/src/shared/data/language-corpora.json +32 -0
  23. package/src/shared/references/pipeline-contracts.md +6 -3
  24. package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
  25. package/src/shared/scripts/skf-comention-pairs.py +350 -0
  26. package/src/shared/scripts/skf-count-tokens.py +222 -0
  27. package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
  28. package/src/shared/scripts/skf-detect-docs.py +240 -4
  29. package/src/shared/scripts/skf-detect-language.py +82 -3
  30. package/src/shared/scripts/skf-detect-tools.py +105 -2
  31. package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
  32. package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
  33. package/src/shared/scripts/skf-extract-public-api.py +53 -0
  34. package/src/shared/scripts/skf-find-cycles.py +246 -0
  35. package/src/shared/scripts/skf-hash-content.py +487 -2
  36. package/src/shared/scripts/skf-language-corpora.py +100 -0
  37. package/src/shared/scripts/skf-manifest-ops.py +67 -1
  38. package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
  39. package/src/shared/scripts/skf-preapply.py +1 -1
  40. package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
  41. package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
  42. package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
  43. package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
  44. package/src/shared/scripts/skf-shape-detect.py +576 -25
  45. package/src/shared/scripts/skf-shard-body.py +461 -0
  46. package/src/shared/scripts/skf-skill-inventory.py +139 -3
  47. package/src/shared/scripts/skf-structural-diff.py +307 -67
  48. package/src/shared/scripts/skf-validate-brief-schema.py +41 -6
  49. package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
  50. package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
  51. package/src/shared/scripts/skf-validate-output.py +443 -10
  52. package/src/shared/scripts/skf-validate-pins.py +3 -3
  53. package/src/shared/scripts/skf-verify-no-trace.py +214 -0
  54. package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
  55. package/src/shared/scripts/skf-write-skill-brief.py +21 -5
  56. package/src/skf-analyze-source/SKILL.md +17 -32
  57. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  58. package/src/skf-analyze-source/customize.toml +2 -0
  59. package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
  60. package/src/skf-analyze-source/references/continue.md +8 -13
  61. package/src/skf-analyze-source/references/generate-briefs.md +28 -12
  62. package/src/skf-analyze-source/references/headless-contract.md +33 -0
  63. package/src/skf-analyze-source/references/health-check.md +1 -2
  64. package/src/skf-analyze-source/references/identify-units.md +25 -36
  65. package/src/skf-analyze-source/references/init.md +12 -25
  66. package/src/skf-analyze-source/references/map-and-detect.md +8 -20
  67. package/src/skf-analyze-source/references/recommend.md +4 -14
  68. package/src/skf-analyze-source/references/scan-project.md +3 -11
  69. package/src/skf-analyze-source/references/step-auto-scope.md +211 -197
  70. package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
  71. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
  72. package/src/skf-audit-skill/SKILL.md +9 -6
  73. package/src/skf-audit-skill/customize.toml +2 -0
  74. package/src/skf-audit-skill/references/init.md +58 -32
  75. package/src/skf-audit-skill/references/re-index.md +15 -34
  76. package/src/skf-audit-skill/references/report.md +11 -58
  77. package/src/skf-audit-skill/references/semantic-diff.md +4 -22
  78. package/src/skf-audit-skill/references/severity-classify.md +53 -87
  79. package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
  80. package/src/skf-audit-skill/references/structural-diff.md +43 -68
  81. package/src/skf-brief-skill/SKILL.md +7 -36
  82. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  83. package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
  84. package/src/skf-brief-skill/customize.toml +14 -0
  85. package/src/skf-brief-skill/references/analyze-target.md +13 -18
  86. package/src/skf-brief-skill/references/confirm-brief.md +2 -15
  87. package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
  88. package/src/skf-brief-skill/references/gather-intent.md +27 -24
  89. package/src/skf-brief-skill/references/headless-args.md +1 -1
  90. package/src/skf-brief-skill/references/health-check.md +1 -22
  91. package/src/skf-brief-skill/references/invocation-contract.md +37 -0
  92. package/src/skf-brief-skill/references/scope-definition.md +6 -15
  93. package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
  94. package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
  95. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  96. package/src/skf-brief-skill/references/write-brief.md +19 -13
  97. package/src/skf-campaign/SKILL.md +121 -0
  98. package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
  99. package/src/skf-campaign/customize.toml +75 -0
  100. package/src/skf-campaign/manifest.yaml +15 -0
  101. package/src/skf-campaign/references/campaign-contracts.md +67 -0
  102. package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
  103. package/src/skf-campaign/references/health-check.md +35 -0
  104. package/src/skf-campaign/references/step-01-setup.md +121 -0
  105. package/src/skf-campaign/references/step-02-strategy.md +102 -0
  106. package/src/skf-campaign/references/step-03-pins.md +56 -0
  107. package/src/skf-campaign/references/step-04-provenance.md +63 -0
  108. package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
  109. package/src/skf-campaign/references/step-06-batch.md +88 -0
  110. package/src/skf-campaign/references/step-07-capstone.md +63 -0
  111. package/src/skf-campaign/references/step-08-verify.md +75 -0
  112. package/src/skf-campaign/references/step-09-refine.md +83 -0
  113. package/src/skf-campaign/references/step-10-export.md +106 -0
  114. package/src/skf-campaign/references/step-11-maintenance.md +84 -0
  115. package/src/skf-campaign/references/step-resume.md +115 -0
  116. package/src/skf-campaign/scripts/.gitkeep +0 -0
  117. package/src/skf-campaign/scripts/campaign-deps.py +244 -0
  118. package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
  119. package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
  120. package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
  121. package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
  122. package/src/skf-campaign/scripts/campaign-report.py +249 -0
  123. package/src/skf-campaign/scripts/campaign-status.py +162 -0
  124. package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
  125. package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
  126. package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
  127. package/src/skf-campaign/templates/campaign-report-template.md +54 -0
  128. package/src/skf-campaign/templates/kickoff-template.md +48 -0
  129. package/src/skf-create-skill/SKILL.md +15 -4
  130. package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
  131. package/src/skf-create-skill/assets/skill-sections.md +9 -100
  132. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
  133. package/src/skf-create-skill/customize.toml +13 -0
  134. package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
  135. package/src/skf-create-skill/references/compile.md +37 -47
  136. package/src/skf-create-skill/references/component-extraction.md +8 -35
  137. package/src/skf-create-skill/references/ecosystem-check.md +9 -25
  138. package/src/skf-create-skill/references/enrich.md +4 -17
  139. package/src/skf-create-skill/references/extract.md +20 -27
  140. package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
  141. package/src/skf-create-skill/references/extraction-patterns.md +10 -8
  142. package/src/skf-create-skill/references/generate-artifacts.md +20 -25
  143. package/src/skf-create-skill/references/health-check.md +2 -2
  144. package/src/skf-create-skill/references/load-brief.md +16 -18
  145. package/src/skf-create-skill/references/report.md +27 -14
  146. package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
  147. package/src/skf-create-skill/references/step-auto-shard.md +52 -25
  148. package/src/skf-create-skill/references/step-doc-rot.md +33 -15
  149. package/src/skf-create-skill/references/step-doc-sources.md +13 -4
  150. package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
  151. package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
  152. package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
  153. package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
  154. package/src/skf-create-skill/references/validate.md +69 -29
  155. package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
  156. package/src/skf-create-stack-skill/SKILL.md +15 -9
  157. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
  158. package/src/skf-create-stack-skill/customize.toml +10 -0
  159. package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
  160. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
  161. package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
  162. package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
  163. package/src/skf-create-stack-skill/references/generate-output.md +24 -75
  164. package/src/skf-create-stack-skill/references/init.md +9 -81
  165. package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
  166. package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
  167. package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
  168. package/src/skf-create-stack-skill/references/report.md +27 -62
  169. package/src/skf-create-stack-skill/references/validate.md +35 -31
  170. package/src/skf-drop-skill/SKILL.md +20 -23
  171. package/src/skf-drop-skill/customize.toml +10 -0
  172. package/src/skf-drop-skill/references/execute.md +67 -36
  173. package/src/skf-drop-skill/references/headless-contract.md +18 -0
  174. package/src/skf-drop-skill/references/health-check.md +1 -13
  175. package/src/skf-drop-skill/references/report.md +44 -10
  176. package/src/skf-drop-skill/references/select.md +93 -48
  177. package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
  178. package/src/skf-export-skill/SKILL.md +17 -14
  179. package/src/skf-export-skill/assets/managed-section-format.md +2 -5
  180. package/src/skf-export-skill/customize.toml +10 -6
  181. package/src/skf-export-skill/references/generate-snippet.md +21 -35
  182. package/src/skf-export-skill/references/load-skill.md +44 -42
  183. package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
  184. package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
  185. package/src/skf-export-skill/references/package.md +27 -33
  186. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
  187. package/src/skf-export-skill/references/result-envelope.md +28 -0
  188. package/src/skf-export-skill/references/summary.md +11 -5
  189. package/src/skf-export-skill/references/token-report.md +42 -24
  190. package/src/skf-export-skill/references/update-context.md +32 -33
  191. package/src/skf-forger/SKILL.md +32 -39
  192. package/src/skf-forger/references/pipeline-mode.md +34 -0
  193. package/src/skf-forger/scripts/parse-pipeline.py +270 -0
  194. package/src/skf-quick-skill/SKILL.md +10 -60
  195. package/src/skf-quick-skill/customize.toml +9 -0
  196. package/src/skf-quick-skill/references/batch-mode.md +6 -4
  197. package/src/skf-quick-skill/references/compile.md +15 -52
  198. package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
  199. package/src/skf-quick-skill/references/finalize.md +12 -6
  200. package/src/skf-quick-skill/references/halt-contract.md +54 -0
  201. package/src/skf-quick-skill/references/health-check.md +3 -4
  202. package/src/skf-quick-skill/references/quick-extract.md +7 -18
  203. package/src/skf-quick-skill/references/registry-resolution.md +1 -23
  204. package/src/skf-quick-skill/references/resolve-target.md +27 -34
  205. package/src/skf-quick-skill/references/write-and-validate.md +10 -25
  206. package/src/skf-refine-architecture/SKILL.md +16 -8
  207. package/src/skf-refine-architecture/customize.toml +9 -0
  208. package/src/skf-refine-architecture/references/compile.md +10 -8
  209. package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
  210. package/src/skf-refine-architecture/references/health-check.md +1 -1
  211. package/src/skf-refine-architecture/references/improvements.md +9 -25
  212. package/src/skf-refine-architecture/references/init.md +9 -16
  213. package/src/skf-refine-architecture/references/issue-detection.md +10 -25
  214. package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
  215. package/src/skf-refine-architecture/references/report.md +14 -11
  216. package/src/skf-rename-skill/SKILL.md +11 -21
  217. package/src/skf-rename-skill/customize.toml +10 -0
  218. package/src/skf-rename-skill/references/execute.md +112 -82
  219. package/src/skf-rename-skill/references/exit-codes.md +12 -0
  220. package/src/skf-rename-skill/references/report.md +16 -8
  221. package/src/skf-rename-skill/references/select.md +69 -42
  222. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
  223. package/src/skf-setup/SKILL.md +21 -13
  224. package/src/skf-setup/customize.toml +10 -0
  225. package/src/skf-setup/references/auto-index.md +11 -10
  226. package/src/skf-setup/references/ccc-index.md +14 -15
  227. package/src/skf-setup/references/detect-and-tier.md +10 -8
  228. package/src/skf-setup/references/report.md +26 -32
  229. package/src/skf-setup/references/tier-rules.md +2 -32
  230. package/src/skf-setup/references/write-config.md +9 -10
  231. package/src/skf-test-skill/SKILL.md +10 -8
  232. package/src/skf-test-skill/customize.toml +2 -0
  233. package/src/skf-test-skill/references/coherence-check.md +19 -31
  234. package/src/skf-test-skill/references/coverage-check.md +111 -67
  235. package/src/skf-test-skill/references/detect-mode.md +10 -41
  236. package/src/skf-test-skill/references/external-validators.md +11 -19
  237. package/src/skf-test-skill/references/init.md +39 -21
  238. package/src/skf-test-skill/references/migration-section-rules.md +4 -4
  239. package/src/skf-test-skill/references/report.md +60 -40
  240. package/src/skf-test-skill/references/score.md +32 -64
  241. package/src/skf-test-skill/references/scoring-rules.md +6 -62
  242. package/src/skf-test-skill/references/source-access-protocol.md +15 -5
  243. package/src/skf-test-skill/references/step-hard-gate.md +1 -3
  244. package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
  245. package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
  246. package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
  247. package/src/skf-test-skill/scripts/compute-score.py +135 -4
  248. package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
  249. package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
  250. package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
  251. package/src/skf-update-skill/SKILL.md +17 -5
  252. package/src/skf-update-skill/customize.toml +11 -6
  253. package/src/skf-update-skill/references/detect-changes.md +48 -36
  254. package/src/skf-update-skill/references/health-check.md +5 -11
  255. package/src/skf-update-skill/references/init.md +40 -32
  256. package/src/skf-update-skill/references/manual-section-rules.md +1 -2
  257. package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
  258. package/src/skf-update-skill/references/merge.md +13 -57
  259. package/src/skf-update-skill/references/re-extract.md +27 -25
  260. package/src/skf-update-skill/references/report.md +14 -7
  261. package/src/skf-update-skill/references/validate.md +32 -30
  262. package/src/skf-update-skill/references/write.md +86 -51
  263. package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
  264. package/src/skf-verify-stack/SKILL.md +11 -21
  265. package/src/skf-verify-stack/customize.toml +12 -3
  266. package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
  267. package/src/skf-verify-stack/references/coverage.md +21 -10
  268. package/src/skf-verify-stack/references/exit-codes.md +14 -0
  269. package/src/skf-verify-stack/references/health-check.md +1 -1
  270. package/src/skf-verify-stack/references/init.md +20 -25
  271. package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
  272. package/src/skf-verify-stack/references/integrations.md +44 -49
  273. package/src/skf-verify-stack/references/report.md +28 -34
  274. package/src/skf-verify-stack/references/requirements.md +3 -3
  275. package/src/skf-verify-stack/references/synthesize.md +36 -39
  276. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
  277. package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
  278. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
  279. package/tools/cli/lib/ui.js +3 -2
  280. package/docs/deepwiki.md +0 -89
  281. package/src/skf-rename-skill/references/rebuild-context.md +0 -110
@@ -1,7 +1,24 @@
1
1
  ---
2
2
  nextStepFile: 'health-check.md'
3
3
  outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
4
- shapeDetectScript: 'src/shared/scripts/skf-shape-detect.py'
4
+ shapeDetectProbeOrder:
5
+ - '{project-root}/_bmad/skf/shared/scripts/skf-shape-detect.py'
6
+ - '{project-root}/src/shared/scripts/skf-shape-detect.py'
7
+ validatePinsProbeOrder:
8
+ - '{project-root}/_bmad/skf/shared/scripts/skf-validate-pins.py'
9
+ - '{project-root}/src/shared/scripts/skf-validate-pins.py'
10
+ skillInventoryProbeOrder:
11
+ - '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
12
+ - '{project-root}/src/shared/scripts/skf-skill-inventory.py'
13
+ scanManifestsProbeOrder:
14
+ - '{project-root}/_bmad/skf/shared/scripts/skf-scan-manifests.py'
15
+ - '{project-root}/src/shared/scripts/skf-scan-manifests.py'
16
+ detectLanguageProbeOrder:
17
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py'
18
+ - '{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'
5
22
  ---
6
23
 
7
24
  <!-- Config: communicate in {communication_language}. -->
@@ -33,7 +50,7 @@ Apply the following heuristic to classify the input:
33
50
  | `github.com/{owner}/{repo}` (with or without `.git` suffix, with or without scheme prefix) | GitHub repo | §1 (standard auto-scope) |
34
51
  | `gitlab.com/...`, `bitbucket.org/...` | Git hosting | §1 (standard auto-scope) |
35
52
  | Starts with `/`, `./`, `~/`, or `~` | Local filesystem path | §1 (standard auto-scope) |
36
- | Any other `https://` or `http://` URL | Documentation URL | §0a (docs-only) |
53
+ | Any other `https://` or `http://` URL | Documentation URL | `references/auto-docs-only.md` (docs-only, via §0c) |
37
54
  | Anything else (SSH URLs, `git://`, bare hostnames, etc.) | Unclassified | §1 (standard auto-scope) |
38
55
 
39
56
  Store the classification result (documentation URL vs. repo/local/other). For all input types, continue to §0b (Pin Resolution).
@@ -48,23 +65,24 @@ This section validates and resolves version pins. It runs for repo URLs and loca
48
65
 
49
66
  **For repo URLs when `--pin` is provided:**
50
67
 
68
+ **Resolve `{validatePinsHelper}`** from `{validatePinsProbeOrder}`; first existing path wins; HALT if neither resolves.
69
+
51
70
  ```bash
52
- uv run python src/shared/scripts/skf-validate-pins.py --repo-url {project_path} --pin {pin_value}
71
+ uv run {validatePinsHelper} --repo-url {project_path} --pin {pin_value}
53
72
  ```
54
73
 
55
74
  Handle exit codes:
56
75
 
57
76
  - **Exit 0** (`status: "valid"`): Store `{pinned_ref}` = `resolved_ref`, `{pinned_ref_type}` = `ref_type`, `{pinned_version}` = `version`. Continue to §0c.
58
- - **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 error envelope:
59
- ```
60
- SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"pin-invalid","mode":"auto"}
61
- ```
62
- - **Exit 2** (error): HARD HALT with exit code 3 (`resolution-failure`). Emit error envelope with `halt_reason: "resolution-failure"`.
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"`.
79
+
80
+ **For repo URLs when `--pin` is not provided (default):**
63
81
 
64
- **For repo URLs when `--pin` is NOT provided (default):**
82
+ Using the same `{validatePinsHelper}` resolved above:
65
83
 
66
84
  ```bash
67
- uv run python src/shared/scripts/skf-validate-pins.py --repo-url {project_path}
85
+ uv run {validatePinsHelper} --repo-url {project_path}
68
86
  ```
69
87
 
70
88
  Handle exit codes:
@@ -79,32 +97,37 @@ This section checks for existing skills matching the target before proceeding. I
79
97
 
80
98
  **1. Load skill inventory:**
81
99
 
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:
103
+
82
104
  ```bash
83
- uv run python src/shared/scripts/skf-skill-inventory.py {skills_output_folder}
105
+ uv run {skillInventoryHelper} {skills_output_folder} --match-target {project_path}
84
106
  ```
85
107
 
86
- 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 to the next section: §0a for documentation URLs, §1 for all other input types.
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.
87
109
 
88
- **2. Match target against existing skills:**
110
+ **2. Read the match set:**
89
111
 
90
- For each skill in the inventory, check two match conditions (either triggers a hit):
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:
91
113
 
92
- - **URL match:** Normalize both the target URL/path and the skill's `metadata.source_repo` — strip scheme (`http://`, `https://`), strip trailing `.git`, strip trailing `/`, compare case-insensitively. A match on the normalized values is a hit.
93
- - **Name match:** Derive the expected skill name from the target (same logic as §6 for repo URLs, §0a for doc URLs — kebab-case from the project/domain name), then compare against each skill's `name`.
114
+ ```json
115
+ { "name": "...", "active_version": "...", "source_repo": "...", "active_path": "...", "match_reason": "url" | "name" | "both" }
116
+ ```
94
117
 
95
- **3. If zero matches:**
118
+ **3. If `matches[]` is empty:**
96
119
 
97
- Complete silently. Continue to §0a for documentation URLs, §1 for all other input types. No user output.
120
+ Complete silently. Continue: execute `references/auto-docs-only.md` for documentation URLs; §1 for all other input types. No user output.
98
121
 
99
- **4. If one or more matches — coexistence gate:**
122
+ **4. If `matches[]` has one or more entries — coexistence gate:**
100
123
 
101
- Present the user with the coexistence decision:
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`):
102
125
 
103
126
  ```
104
127
  ⚠️ Existing skill(s) found for {target_name}:
105
128
 
106
129
  • {skill_name} (v{version}) — source: {source_repo}
107
- [repeat for each match]
130
+ [repeat for each entry in matches[]]
108
131
 
109
132
  Actions:
110
133
  [A]longside — Create a new wiki skill with "-wiki" suffix (existing skill untouched)
@@ -118,201 +141,160 @@ In headless mode (`{headless_mode}` is true): auto-select `[A]longside` and log:
118
141
 
119
142
  **5. Handle user selection:**
120
143
 
121
- - **[A]longside:** Set `{coexistence_suffix}` to `-wiki`. Continue to §0a for documentation URLs, §1 for all other input types. The existing skill is untouched.
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.
122
145
 
123
- - **[M]erge:** If multiple skills match, prompt the user to select which one to merge into before proceeding. Emit a redirect envelope signaling the forger to route to the US workflow for the selected skill:
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:
124
147
  ```
125
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"}
126
149
  ```
127
150
  Write the result contract per `shared/references/output-contract-schema.md` with `status: "redirect"`.
128
- Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to §0a or §1.**
151
+ Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.**
129
152
 
130
153
  - **[S]kip:** Emit a skip envelope:
131
154
  ```
132
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}"}
133
156
  ```
134
157
  Write the result contract with `status: "skipped"`.
135
- Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to §0a or §1.**
136
-
137
- ### 0a. Docs-Only Short-Circuit
158
+ Chain to {nextStepFile} (health-check.md). **STOP HERE — do not proceed to the docs-only sub-flow or §1.**
138
159
 
139
- This section handles documentation URLs that are not GitHub repos or local paths. It validates the URL, writes a minimal brief and analysis report, emits the envelope, and chains directly to health-check — skipping §1 through §11 entirely.
160
+ ### 1. Load Context
140
161
 
141
- **1. Validate URL reachability:**
162
+ Read {outputFile} frontmatter to obtain:
163
+ - `project_paths[]` — the root(s) to analyze
164
+ - `forge_tier` — for brief generation
165
+ - `project_name`, `user_name`, `date`
142
166
 
143
- ```bash
144
- curl -sI --max-time 5 {url}
145
- ```
167
+ Load `references/step-shape-detect.md` as reference for shape detection invocation contract and shape→scope mapping.
146
168
 
147
- - On **2xx/3xx** response: URL is reachable. Continue.
148
- - On **4xx/5xx**, DNS failure, or timeout: HARD HALT with exit code 3 (`resolution-failure`). Emit error message: `"Documentation URL unreachable: {url} — {status or error}"`. Emit error envelope:
149
- ```
150
- SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"path-invalid","mode":"auto","source_type":"docs-only"}
151
- ```
169
+ ### 2. Manifest Scan
152
170
 
153
- **2. Derive skill name from URL domain:**
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:
154
172
 
155
- Extract the hostname from the URL (e.g., `docs.example.com` from `https://docs.example.com/guide/intro`), convert to kebab-case (replace `.` with `-`), yielding e.g. `docs-example-com`. If `{coexistence_suffix}` is non-empty, append it to the skill name (e.g., `docs-example-com-wiki`).
173
+ **For each path in `project_paths[]`:**
156
174
 
157
- **3. Write analysis report:**
175
+ - **Local filesystem path** (starts with `/`, `./`, `~/`, `~`, or is an existing directory) — scan it directly:
158
176
 
159
- Update {outputFile} with docs-only results.
177
+ ```bash
178
+ uv run {scanManifestsHelper} scan {path}
179
+ ```
160
180
 
161
- **Update frontmatter:**
162
- ```yaml
163
- stepsCompleted: ['init', 'auto-scope']
164
- lastStep: 'auto-scope'
165
- source_type: docs-only
166
- confirmed_units:
167
- - name: '{skill_name}'
168
- shape: 'docs-only'
169
- confidence: 1.0
170
- export_count: 0
171
- package_count: 0
172
- ```
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:
173
182
 
174
- **Append body section:**
175
- ```markdown
176
- ## Auto-Scope Analysis
183
+ ```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"
189
+ ```
177
190
 
178
- **Mode:** auto (docs-only short-circuit)
179
- **Source Type:** docs-only
180
- **Documentation URL:** {url}
181
- **Skill Name:** {skill_name}
182
- ```
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.
194
+
195
+ From the envelope, record:
196
+
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.
199
+
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):
201
+
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
+ ```
183
224
 
184
- **4. Write skill brief via canonical writer:**
225
+ Record `<grammar_matches>` and `<tree_paths>` (each a comma-joined list, possibly empty) for §3.
185
226
 
186
- Create directory `{forge_data_folder}/{skill_name}/` if it does not exist.
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`.
187
230
 
188
- Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` through the canonical writer (`skf-write-skill-brief.py`) with the following context:
231
+ ### 3. Invoke Shape Detection
189
232
 
190
- ```json
191
- {
192
- "name": "{skill_name}",
193
- "target_version": null,
194
- "detected_version": null,
195
- "source_type": "docs-only",
196
- "source_repo": "{url}",
197
- "language": "",
198
- "description": "Skill created from documentation at {url}",
199
- "forge_tier": "{forge_tier}",
200
- "created": "{current_date}",
201
- "created_by": "{user_name}",
202
- "scope_type": "docs-only",
203
- "scope_include": [],
204
- "scope_exclude": [],
205
- "scope_notes": "Docs-only skill created from documentation URL",
206
- "scope_rationale": null,
207
- "scope_tier_a_include": null,
208
- "scope_amendments": null,
209
- "doc_urls": [{"url": "{url}", "label": "Primary Documentation"}],
210
- "scripts_intent": null,
211
- "assets_intent": null,
212
- "source_authority": "community",
213
- "target_ref": null,
214
- "source_ref": null,
215
- "version_resolved": "1.0.0"
216
- }
217
- ```
233
+ **Resolve `{shapeDetectHelper}`** from `{shapeDetectProbeOrder}`; first existing path wins; HALT if neither resolves.
218
234
 
219
- **5. Emit success envelope:**
235
+ Invoke the shape detection script with the discovered manifests and the harvested tree-level signals:
220
236
 
221
237
  ```
222
- SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_path}","brief_paths":["{brief_path}"],"unit_counts":{"confirmed":1,"skipped":0,"maybe":0},"exit_code":0,"halt_reason":null,"mode":"auto","source_type":"docs-only"}
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>
223
241
  ```
224
242
 
225
- If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
226
-
227
- The `source_type` field signals downstream consumers (BS) to skip repo-based enrichment.
228
-
229
- **6. Write result contract** per `shared/references/output-contract-schema.md`: the per-run record and latest copy, same as §10.
230
-
231
- If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
232
-
233
- **7. Chain to health check:**
234
-
235
- Load, read fully, then execute {nextStepFile} to run the shared workflow health check. **Skip §1 through §11 entirely.**
236
-
237
- ### 1. Load Context
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}`
238
244
 
239
- Read {outputFile} frontmatter to obtain:
240
- - `project_paths[]` — the root(s) to analyze
241
- - `forge_tier` — for brief generation
242
- - `project_name`, `user_name`, `date`
243
-
244
- Load `references/step-shape-detect.md` as reference for shape detection invocation contract and shape→scope mapping.
245
-
246
- ### 2. Manifest Scan
247
-
248
- Perform a lightweight manifest scan — find standard package manifests in the project root and workspace paths. Do NOT crawl the full directory tree.
245
+ **Handle exit codes:**
249
246
 
250
- **For each path in `project_paths[]`:**
247
+ - **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"`.
251
250
 
252
- 1. Check the project root for: `package.json`, `pyproject.toml`, `Cargo.toml`
253
- 2. If a workspace configuration exists (e.g., `pnpm-workspace.yaml`, Cargo.toml `[workspace].members`), scan workspace member paths for additional manifests
254
- 3. Record each discovered manifest as `{path, type}` pairs
251
+ ### 3a. Check Decomposition Thresholds
255
252
 
256
- **IF no manifests found:**
257
- - Emit fallback message: "**Auto-scope could not find any package manifests — switching to interactive mode.**"
258
- - Load, read fully, then execute `references/scan-project.md`. **STOP HERE.**
253
+ Evaluate the shape detection output to determine whether this **monorepo** should be decomposed into multiple skills.
259
254
 
260
- ### 3. Invoke Shape Detection
255
+ Apply the **Decomposition Thresholds** ladder from `step-shape-detect.md` (loaded at §1). A *single* package with a large API surface is **not** a trigger — only a genuine multi-package monorepo is.
261
256
 
262
- Invoke the shape detection script with discovered manifests:
257
+ **Decision:**
263
258
 
264
- ```
265
- uv run python {shapeDetectScript} --repo-url <project_path_or_url> --manifests <comma_separated_manifest_paths>
266
- ```
259
+ - **Threshold not met** (`package_count ≤ 3`) → Continue to §4 (single-scope flow, entirely unchanged).
260
+ - **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)".
267
261
 
268
- Parse the JSON output: `{shape, signals, confidence, export_count, package_count}`
262
+ ### 3b. Cohesion Check Merge to One Skill vs Split into N
269
263
 
270
- **Handle exit codes:**
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:
271
265
 
272
- - **Exit 0 (shape classified):** Continue to §3a.
273
- - **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.**
274
- - **Exit 2 (error):** HARD HALT with exit code 3 (`resolution-failure`). Emit the error envelope:
275
- ```
276
- SKF_ANALYZE_RESULT_JSON: {"status":"error","report_path":null,"brief_paths":[],"unit_counts":{"confirmed":0,"skipped":0,"maybe":0},"exit_code":3,"halt_reason":"resolution-failure","mode":"auto"}
277
- ```
266
+ **Merge into ONE cohesive skill** (override the threshold → continue to §4 single-scope) when **any** of these hold:
278
267
 
279
- ### 3a. Check Decomposition Thresholds
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.
280
271
 
281
- Evaluate the shape detection output to determine whether this repo should be decomposed into multiple skills.
272
+ **Split into N skills** (→ §4a) when:
282
273
 
283
- **Threshold conditions** (both constants are `[PENDING VALIDATION]` subject to empirical tuning):
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.
284
275
 
285
- | Threshold | Condition | Signal |
286
- |-----------|-----------|--------|
287
- | Large export surface | `export_count > 500` `[PENDING VALIDATION]` | Single skill covering 500+ exports produces unwieldy output |
288
- | Monorepo / multi-package | `package_count > 3` `[PENDING VALIDATION]` | 4+ packages are almost always multi-concern |
276
+ If genuinely unsure, **prefer merge** a too-broad single skill is recoverable with `US`; N fragmented skills are not.
289
277
 
290
- **Decision:**
278
+ **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:
291
279
 
292
- - **Neither threshold met** Continue to §4 (single-scope flow, entirely unchanged).
293
- - **Either or both thresholds met** Route to §4a (multi-scope decomposition flow). Log: "Auto-decomposition triggered: {reason} ({value} exceeds threshold {threshold} `[PENDING VALIDATION]`)" where reason is `export_threshold`, `package_threshold`, or `both`.
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."_
281
+ - Surface the excluded facets in the analysis report (§7) so the operator can re-scope or forge a companion skill.
294
282
 
295
283
  ### 4. Map Shape to Scope
296
284
 
297
- Apply the shape→scope.type mapping:
298
-
299
- | Shape (from skf-shape-detect.py) | scope.type | Condition |
300
- |----------------------------------|------------|-----------|
301
- | `library-API` | `full-library` | export_count ≤ 200 |
302
- | `library-API` | `public-api` | export_count > 200 |
303
- | `reference-app` | `reference-app` | — |
304
- | `language-reference` | `full-library` | — |
305
- | `stack-compose` | `full-library` | Multi-scope via §3a when `package_count > 3` `[PENDING VALIDATION]` |
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).
306
286
 
307
287
  ### 5. Generate Include/Exclude Patterns
308
288
 
309
289
  Generate `scope.include` and `scope.exclude` arrays from the detected language and project structure.
310
290
 
311
- **Detect primary language** from manifest type:
312
- - `package.json` → TypeScript/JavaScript
313
- - `pyproject.toml` → Python
314
- - `Cargo.toml` Rust
315
- - `go.mod` → Go
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:
292
+
293
+ ```bash
294
+ echo '{"tree": [<§2 supported manifest paths + harvested tree paths>]}' | uv run {detectLanguageHelper}
295
+ ```
296
+
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.
316
298
 
317
299
  **Default patterns (adjust based on actual project structure):**
318
300
 
@@ -321,6 +303,10 @@ Generate `scope.include` and `scope.exclude` arrays from the detected language a
321
303
  | TypeScript/JavaScript | `['src/**/*.ts', 'src/**/*.tsx']` | `['**/*.test.ts', '**/*.spec.ts', '**/node_modules/**']` |
322
304
  | Python | `['src/**/*.py']` or `['{package_name}/**/*.py']` | `['**/*_test.py', '**/test_*.py', '**/tests/**']` |
323
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/**']` |
324
310
 
325
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.
326
312
 
@@ -332,41 +318,53 @@ scope:
332
318
  type: '{mapped_scope_type}'
333
319
  include: ['{generated_include_patterns}']
334
320
  exclude: ['{generated_exclude_patterns}']
335
- notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence})'
321
+ notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}'
336
322
  ```
337
323
 
338
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.
339
325
 
340
- Detect the primary language from the manifest ecosystem:
341
- - `npm` → `typescript` (or `javascript` if no `.ts` files in includes)
342
- - `python` → `python`
343
- - `rust` → `rust`
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).
344
327
 
345
- ### 4a. Multi-Scope Decomposition
328
+ ### 6b. Seed Companion Corpora (whole-language references only)
346
329
 
347
- This section is reached only from §3a when one or both decomposition thresholds are met. It replaces §4→§5→§6 for repos that will produce N > 1 skills.
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.
348
331
 
349
- **Determine decomposition path:**
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.
350
333
 
351
- - **Monorepo path** (`package_count > 3`): 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.
352
- - **Large-export path** (`export_count > 500`, single package): Group by top-level source directory modules (e.g., `src/auth/`, `src/core/`, `src/api/`). Each directory subtree with a meaningful export surface becomes a separate skill boundary. Candidate boundaries with fewer than ~50 exports `[PENDING VALIDATION]` should be merged into an "other" catch-all skill rather than becoming standalone skills. Name each skill as `{project_name}-{module_name}` (kebab-case); if `{coexistence_suffix}` is non-empty, append it. If no clear module structure exists (flat `src/` with all files at root level), **do not force decomposition** — fall back to single-scope flow at §4.
353
- - **Combined path** (both thresholds met): Use the monorepo path. Package boundaries are explicit and take priority over export-count grouping (which is heuristic).
334
+ **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}` and **`{languageCorporaHelper}`** from `{languageCorporaProbeOrder}` (first existing path wins).
354
335
 
355
- **Per-boundary shape→scope mapping:**
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.
356
354
 
357
- For each decomposed boundary, apply the shape→scope mapping from §4 independently — the boundary's local characteristics determine its scope.type:
355
+ ### 4a. Multi-Scope Decomposition
358
356
 
359
- | Decomposition Type | Per-Boundary Shape Mapping |
360
- |--------------------|---------------------------|
361
- | Monorepo (`package_count > 3`) | 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). |
362
- | Large-export (`export_count > 500`) | All boundaries inherit the parent shape. scope.type varies by per-boundary export count (e.g., ≤200 → `full-library`, >200 → `public-api`). |
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.
363
358
 
364
- ### 5a. Generate Multi-Scope Patterns
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.
360
+
361
+ **Per-boundary shape→scope mapping:**
362
+
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).
365
364
 
366
- For each decomposed boundary, generate include/exclude patterns using the same language-aware rules as §5, but scoped to the boundary's source paths.
365
+ ### 5a. Generate Multi-Scope Patterns
367
366
 
368
- - Monorepo boundaries: patterns are rooted at the package path (e.g., `packages/auth/src/**/*.ts` instead of `src/**/*.ts`)
369
- - Large-export boundaries: patterns are rooted at the module directory (e.g., `src/auth/**/*.ts`)
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`).
370
368
 
371
369
  ### 6a. Build Multi-Scope
372
370
 
@@ -382,7 +380,7 @@ After building all N scopes, continue to §7 with the full set of boundaries.
382
380
 
383
381
  ### 7. Write Analysis Report
384
382
 
385
- Update {outputFile} with auto-scope results.
383
+ Update {outputFile} with auto-scope results. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`.
386
384
 
387
385
  **Update frontmatter:**
388
386
  ```yaml
@@ -404,11 +402,11 @@ Add `decomposition` to frontmatter:
404
402
  ```yaml
405
403
  decomposition:
406
404
  triggered: true
407
- reason: 'export_threshold' | 'package_threshold' | 'both'
405
+ reason: 'package_threshold'
408
406
  boundary_count: N
409
407
  ```
410
408
 
411
- Each `confirmed_units` entry includes `boundary_path` — the relative path to the boundary's root (e.g., `packages/core` for monorepo, `src/auth` for large-export). Omit the `decomposition` key entirely when single-scope (N = 1).
409
+ 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).
412
410
 
413
411
  **When single-scope (N = 1):** No `decomposition` key. `confirmed_units` contains a single entry (existing behavior).
414
412
 
@@ -428,6 +426,17 @@ For single-scope (unchanged):
428
426
  **Exclude Patterns:** {exclude patterns}
429
427
  ```
430
428
 
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:
430
+
431
+ ```markdown
432
+ ## Companion Corpora (language-reference)
433
+
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
+
431
440
  For multi-scope (N > 1):
432
441
  ```markdown
433
442
  ## Auto-Scope Analysis — Decomposition ({N} skills)
@@ -453,9 +462,9 @@ For multi-scope (N > 1):
453
462
 
454
463
  **For each confirmed unit** (1 for single-scope, N for decomposition):
455
464
 
456
- Create directory `{forge_data_folder}/{skill_name}/` if it does not exist.
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`.
457
466
 
458
- Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` conforming to the skill-brief schema (`assets/skill-brief-schema.md`):
467
+ Write `{forge_data_folder}/{skill_name}/skill-brief.yaml` conforming to the skill-brief schema (`{briefSchemaPath}`):
459
468
 
460
469
  ```yaml
461
470
  name: '{skill_name}'
@@ -468,13 +477,22 @@ scope:
468
477
  - '{include_patterns}'
469
478
  exclude:
470
479
  - '{exclude_patterns}'
471
- notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence})'
480
+ notes: 'Auto-scoped from shape detection (shape: {shape}, confidence: {confidence}).{corpus_caveat}'
472
481
  description: '{1-3 sentence description based on shape, language, and manifest name}'
473
482
  forge_tier: '{forge_tier}'
474
483
  created: '{current_date}'
475
484
  created_by: '{user_name}'
476
485
  ```
477
486
 
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'
492
+ ```
493
+
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
+
478
496
  **When decomposition is active (N > 1 units):**
479
497
 
480
498
  Loop over all N boundaries. For each boundary:
@@ -484,7 +502,7 @@ Loop over all N boundaries. For each boundary:
484
502
  - `description` references the parent project and boundary role (e.g., "Core library package of the my-monorepo project, providing...")
485
503
  - All N briefs share the same `version`, `source_repo`, `language`, `forge_tier`, `created`, `created_by` values as the parent project
486
504
 
487
- **Version detection:** Attempt to auto-detect the source version per the version detection rules in `assets/skill-brief-schema.md`. Fall back to `1.0.0` if detection fails.
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.
488
506
 
489
507
  **Pin data (from §0b):** When `{pinned_ref}` is non-null, enrich the brief with pin data:
490
508
 
@@ -494,7 +512,7 @@ Loop over all N boundaries. For each boundary:
494
512
 
495
513
  When `{pinned_ref}` is null (no pin, no releases): leave `target_version` = null, `target_ref` = null — existing version detection applies unchanged.
496
514
 
497
- In the docs-only path (§0a), `--pin` is ignored (already skipped at §0b). No changes to §0a.
515
+ In the docs-only path (`references/auto-docs-only.md`), `--pin` is ignored (already skipped at §0b). No changes to that path.
498
516
 
499
517
  ### 9. Emit Result Envelope
500
518
 
@@ -504,22 +522,18 @@ Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stdout:
504
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"}
505
523
  ```
506
524
 
507
- `brief_paths` contains N paths (one per confirmed unit). `unit_counts.confirmed` is N. The envelope JSON format is structurally unchanged — `brief_paths` was already an array and `unit_counts.confirmed` was already a number. No breaking change for downstream consumers.
525
+ `brief_paths` contains N paths (one per confirmed unit). `unit_counts.confirmed` is N.
508
526
 
509
527
  If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
510
528
 
511
- 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 (backward-compatible — existing consumers don't expect them).
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.
512
530
 
513
531
  ### 10. Write Result Contract
514
532
 
515
- 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.
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`.
516
534
 
517
535
  If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
518
536
 
519
537
  ### 11. Chain to Health Check
520
538
 
521
539
  Load, read fully, then execute {nextStepFile} to run the shared workflow health check.
522
-
523
- ## CRITICAL STEP COMPLETION NOTE
524
-
525
- ONLY WHEN the analysis report has been updated, the skill-brief.yaml written and validated, the result envelope emitted, and the result contract saved will you load and read fully {nextStepFile} to begin the health check.