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
@@ -51,7 +51,10 @@ Context payload shape (consumed by `write`):
51
51
  },
52
52
 
53
53
  # Conditionally present:
54
- "doc_urls": [{"url": "...", "label": "..."}],
54
+ "doc_urls": [{"url": "...", "label": "...", "source": "..."}],
55
+ # `source` (optional per #432): language-registry | readme-detection |
56
+ # homepage | pages-api | docs-folder. Threaded through when present;
57
+ # absent → entry renders as {url, label} only (no false drift).
55
58
  "scripts_intent": "detect" | "none" | free-text,
56
59
  "assets_intent": "detect" | "none" | free-text,
57
60
  "source_authority": "official" | "community" | "internal",
@@ -95,7 +98,7 @@ Flat input form (`--from-flat`):
95
98
  "scope_notes": "",
96
99
  "scope_tier_a_include": null | ["code/core/src/**"],
97
100
  "scope_amendments": null | [{...}],
98
- "doc_urls": null | [...],
101
+ "doc_urls": null | [{"url": "...", "label": "...", "source": "..."}],
99
102
  "scripts_intent": null | "detect" | "none" | "...",
100
103
  "assets_intent": null | "detect" | "none" | "...",
101
104
  "source_authority": null | "official" | "community" | "internal",
@@ -433,11 +436,24 @@ def assemble_brief(ctx: dict[str, Any], resolved_version: str) -> dict[str, Any]
433
436
  brief[ref_field] = ref_val
434
437
 
435
438
  # Conditional: doc_urls (always emitted when present)
439
+ #
440
+ # Each entry carries the {url, label} contract plus an OPTIONAL `source`
441
+ # provenance field (issue #432: language-registry | readme-detection |
442
+ # homepage | pages-api | docs-folder). `source` is emitted ONLY when present
443
+ # on the input entry — mirroring the source_authority null-drop discipline
444
+ # below (lines ~448-454): a legacy/hand-authored entry without `source`
445
+ # round-trips to exactly {url, label}, so a ratify/re-write of an old brief
446
+ # stays byte-identical. (An explicit empty-string source is treated as
447
+ # absent here; the schema enum is the real gate and rejects "" upstream.)
436
448
  doc_urls = ctx.get("doc_urls")
437
449
  if doc_urls:
438
- brief["doc_urls"] = [
439
- {"url": e["url"], "label": e.get("label", "")} for e in doc_urls
440
- ]
450
+ rendered_doc_urls: list[dict[str, Any]] = []
451
+ for e in doc_urls:
452
+ entry = {"url": e["url"], "label": e.get("label", "")}
453
+ if e.get("source"):
454
+ entry["source"] = e["source"]
455
+ rendered_doc_urls.append(entry)
456
+ brief["doc_urls"] = rendered_doc_urls
441
457
 
442
458
  # Conditional: scripts_intent / assets_intent — emit when explicitly non-detect
443
459
  for intent_field in ("scripts_intent", "assets_intent"):
@@ -19,7 +19,7 @@ Analyzes a large repo or multi-service project to identify discrete skillable un
19
19
 
20
20
  ## Role
21
21
 
22
- You are a source code analyst and decomposition architect collaborating with a developer onboarding an existing project. You bring expertise in codebase analysis, service boundary detection, and skill scoping, while the user brings their domain knowledge. Work together as equals.
22
+ You are a source code analyst and decomposition architect collaborating with a developer onboarding an existing project, pairing your codebase-analysis and skill-scoping expertise with their domain knowledge.
23
23
 
24
24
  ## Workflow Rules
25
25
 
@@ -34,16 +34,16 @@ These rules apply to every step in this workflow:
34
34
  | # | Step | File | Auto-proceed | Condition |
35
35
  |---|------|------|--------------|-----------|
36
36
  | 1 | Initialize | references/init.md | Yes | Always |
37
- | 1a | Auto-Scope | references/step-auto-scope.md | Yes | `[auto]` mode only. Includes §0b pin resolution validates and resolves version pin before scoping. Includes §0c coexistence detection checks for existing skills before proceeding. Includes §0 docs-only URL detection — doc URLs short-circuit auto-scope entirely |
37
+ | 1a | Auto-Scope | references/step-auto-scope.md | Yes | `[auto]` mode only — bypasses steps 2–6; owns pin resolution, coexistence detection, and the docs-only short-circuit |
38
38
  | 1b | Continue (session resume) | references/continue.md | Yes | Always |
39
39
  | 2 | Scan Project | references/scan-project.md | No (confirm) | Interactive mode only |
40
40
  | 3 | Identify Units | references/identify-units.md | No (confirm) | Interactive mode only |
41
41
  | 4 | Map & Detect | references/map-and-detect.md | Yes | Interactive mode only |
42
42
  | 5 | Recommend | references/recommend.md | No (confirm) | Interactive mode only |
43
- | 6 | Generate Briefs | references/generate-briefs.md | Yes | Interactive mode only |
43
+ | 6 | Generate Briefs | references/generate-briefs.md | No (confirm) | Interactive mode only |
44
44
  | 7 | Workflow Health Check | references/health-check.md | Yes | Always |
45
45
 
46
- **Auto mode path:** When `[auto]` flag is present, init (step 1) routes directly to step 1a, which performs manifest scan → shape detection → scope generation → brief write → health check, bypassing steps 2–6. After URL type detection (§0), pin resolution (§0b) validates the `--pin` argument (or resolves the latest release tag) and stores the resolved ref for downstream brief writes. Coexistence detection (§0c) then checks for existing skills matching the target. If found, the user chooses alongside/merge/skip. Headless mode auto-selects alongside. Auto-scope may produce N > 1 confirmed units when decomposition thresholds are met (`export_count > 500` `[PENDING VALIDATION]` or `package_count > 3` `[PENDING VALIDATION]`), resulting in N briefs and N `brief_paths` in the envelope. When the target is a documentation URL (not a GitHub repo or local path), auto-scope detects the docs-only input at §0, validates URL reachability, writes a docs-only brief, and emits the envelope without performing source analysis.
46
+ **Auto mode path:** With `[auto]` present, init routes directly to step 1a. Step 1a may confirm N > 1 units (a monorepo splits into N briefs / N `brief_paths`, or merges to one), and routes docs-only targets to `references/auto-docs-only.md`.
47
47
 
48
48
  **Shape detection reference:** `references/step-shape-detect.md` — loaded by step 1a as a reference doc (not a chained step).
49
49
 
@@ -51,41 +51,24 @@ These rules apply to every step in this workflow:
51
51
 
52
52
  | Aspect | Detail |
53
53
  |--------|--------|
54
- | **Inputs** | project_path [required], scope_hint [optional]. `project_path` can be a GitHub repo URL, a local filesystem path, or a documentation URL for docs-only mode. The URL type heuristic at §0 determines the mode. |
55
- | **Headless inputs** | `--project-path <path>` (skip Step 1 project-path prompt; accepts documentation URLs for docs-only mode), `--scope-hint <text>` (skip Step 1 scope-hint prompt), `--intent-hint <text>` (pre-supply analysis intent; drives recommendation ranking in Step 5), `--pin <version>` (pin to a specific version tag or branch; accepts semver tags, git tags, and branch names; when absent, resolves to the latest release tag) |
54
+ | **Inputs** | project_path [required], scope_hint [optional]. `project_path` is a GitHub repo URL or a local filesystem path. In `[auto]` mode it may also be a documentation URL step 1a classifies the URL type and routes docs-only targets (see Stages row 1a). |
55
+ | **Headless inputs** | `--project-path <path>` (skip Step 1 project-path prompt; in `[auto]` mode also accepts documentation URLs for docs-only mode), `--scope-hint <text>` (skip Step 1 scope-hint prompt), `--intent-hint <text>` (pre-supply analysis intent; drives recommendation ranking in Step 5), `--pin <version>` (`[auto]` mode only — pin to a specific version tag or branch; accepts semver tags, git tags, and branch names; when absent, resolves to the latest release tag; interactive/headless runs use `--target-refs`/`--target-ref` instead) |
56
56
  | **Headless flag** | `--headless` / `-H` flips every confirm gate to auto-proceed |
57
- | **Auto flag** | `[auto]` bracket modifier — activates auto-scope mode (step 1a). Pipelines pass this as `AN[auto]`. When active, init routes to `step-auto-scope.md` which performs shape detection → scope generation → brief write, bypassing interactive steps 2–6. Requires `--project-path`. |
58
- | **Gates** | step 2: Confirm Gate [C] | step 3: Confirm Gate [C] | step 5: Confirm Gate [C] (all skipped in auto mode) |
57
+ | **Auto flag** | `[auto]` bracket modifier — activates auto-scope mode (step 1a; see **Auto mode path** above). Pipelines pass this as `AN[auto]`. Requires `--project-path`. |
58
+ | **Gates** | steps 2/3/5: Confirm Gate [C]; step 6: Confirm Gate [Y] (write briefs) all skipped in auto mode |
59
59
  | **Outputs** | analysis-report.md, skill-brief.yaml files (one per recommended unit); final `SKF_ANALYZE_RESULT_JSON` line on stdout when `{headless_mode}` is true. In auto mode, the envelope includes `"mode":"auto"`. |
60
60
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
61
- | **Exit codes** | See "Exit Codes" below |
61
+ | **Exit codes** | See `references/headless-contract.md` |
62
62
 
63
- ## Exit Codes
63
+ ## Headless Result Contract
64
64
 
65
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
66
-
67
- | Code | Meaning | Raised by |
68
- | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
69
- | 0 | success / skipped / redirect | step 7 (terminal — health check completion); also covers coexistence `"skipped"` and `"redirect"` statuses from §0c |
70
- | 2 | input-missing | step 1 §2-3 — required config absent (config.yaml not loadable, project path empty/invalid in headless mode); step 1 §2b — auto mode without `--project-path` |
71
- | 3 | resolution-failure | step 1 §2 (`forge-tier.yaml` missing at `{sidecar_path}/forge-tier.yaml`); step 1 §3 (project path does not exist or remote URL inaccessible); step 1a §0a (docs-only URL unreachable); step 1a §0b (pin validation failure — `halt_reason: "pin-invalid"` when the supplied `--pin` does not match any tag or branch); step 1a §3 (shape detection script error, exit code 2) |
72
- | 4 | write-failure | step 1 §6 (analysis report write failed); step 6 §5 (skill-brief.yaml write failed); step 6 §9 (result contract write failed) |
73
- | 6 | user-cancelled | any interactive menu in steps 2/3/5/6 (user selected `[X]` Cancel and exit) |
74
-
75
- ## Result Contract (Headless)
76
-
77
- When `{headless_mode}` is true, step 6 (interactive) or step 1a (auto) emits a single-line JSON envelope on **stdout** before chaining to step 7, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
78
-
79
- ```
80
- SKF_ANALYZE_RESULT_JSON: {"status":"success|error","report_path":"…|null","brief_paths":["…"],"unit_counts":{"confirmed":N,"skipped":N,"maybe":N},"exit_code":0,"halt_reason":null,"mode":"interactive|auto"}
81
- ```
82
-
83
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT, `"redirect"` when coexistence detection routes to US (merge), or `"skipped"` when the user skips a conflicting target. `halt_reason` is one of: `null` (success), `"input-missing"`, `"forge-tier-missing"`, `"path-invalid"`, `"pin-invalid"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above. `brief_paths` is an array of absolute paths to every generated `skill-brief.yaml` (empty array if none were generated). `unit_counts` reports confirmed/skipped/maybe counts from step 5's user decisions. `mode` is `"auto"` when the `[auto]` flag was active, `"interactive"` otherwise (omitting `mode` is equivalent to `"interactive"` for backward compatibility). The `coexistence` field (present when §0c triggers) is `"alongside"`, `"merge"`, or `"skip"`, indicating the user's coexistence decision. The `pinned_ref` and `pinned_version` fields (present when §0b resolves a pin) record the resolved git ref and extracted semver version for provenance tracking.
65
+ Headless/pipeline runs emit a single-line `SKF_ANALYZE_RESULT_JSON` envelope — on **stdout** for the terminal success path (step 6 or step 1a), on **stderr** with `status: "error"` for every HARD HALT and exit with a stable code per failure class. The envelope shape, the `halt_reason` enum, and the exit-code table live in `references/headless-contract.md`; step files emit the concrete instance at each site.
84
66
 
85
67
  ## On Activation
86
68
 
87
69
  1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
88
70
  - `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`, `forge_data_folder`, `skills_output_folder`, `sidecar_path`
71
+ - If the config cannot be loaded, HARD HALT with exit code 2 (`input-missing`) per `references/headless-contract.md` — the workflow has no forge context to run against.
89
72
 
90
73
  2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in preferences.yaml. Default: false.
91
74
 
@@ -104,13 +87,15 @@ SKF_ANALYZE_RESULT_JSON: {"status":"success|error","report_path":"…|null","bri
104
87
 
105
88
  If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly — the bundled defaults are an empty string for each path scalar.
106
89
 
107
- Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each scalar, if the merged value is empty or absent, use the bundled default:
90
+ Apply the path-scalar fallback now, so stage files reference the resolved variable with no conditional at the usage site. For each scalar, if the merged value is empty or absent, use the bundled default:
108
91
 
109
92
  - `{unitDetectionHeuristicsPath}` ← `workflow.unit_detection_heuristics_path` if non-empty, else `references/unit-detection-heuristics.md`
110
93
  - `{briefSchemaPath}` ← `workflow.brief_schema_path` if non-empty, else `assets/skill-brief-schema.md`
111
94
  - `{analysisReportTemplatePath}` ← `workflow.analysis_report_template_path` if non-empty, else `templates/analysis-report-template.md`
112
- - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — workflow skips the hook invocation)
95
+ - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (hook invocation skipped)
96
+
97
+ Stash all four as workflow-context variables. A non-empty value lets an org swap in a house-style copy (or wire a pipeline hook) without forking the skill.
113
98
 
114
- Stash all four as workflow-context variables. Stage files reference `{unitDetectionHeuristicsPath}` / `{briefSchemaPath}` / `{analysisReportTemplatePath}` / `{onCompleteCommand}` directly no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies (or wire in pipeline hooks) without forking the skill.
99
+ Apply the array surfaces too: run `workflow.activation_steps_prepend` now, treat `workflow.persistent_facts` as standing context for the run (`file:`-prefixed entries load their file/glob contents as facts), then run `workflow.activation_steps_append` after activation.
115
100
 
116
101
  4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
@@ -23,7 +23,7 @@ Defines the output contract for skill-brief.yaml files generated by analyze-sour
23
23
  | Field | Type | Constraint | Description |
24
24
  |--------------------|--------|--------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
25
25
  | source_type | string | `source` or `docs-only` | Default `source`. When `docs-only`: `source_repo` optional, `doc_urls` required |
26
- | doc_urls | array | `{url, label}` objects | Documentation URLs for T3 content. Required when `source_type: "docs-only"` |
26
+ | doc_urls | array | `{url, label, source?}` objects | Documentation URLs for T3 content. Required when `source_type: "docs-only"`. Optional `source` provenance enum: `language-registry` (registry-guaranteed corpus) \| `readme-detection` \| `homepage` \| `pages-api` \| `docs-folder` |
27
27
  | `scripts_intent` | string | `detect` / `none` / free-text | Describes whether scripts should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip scripts), or a free-text description of expected scripts (e.g., "CLI validation tools in bin/"). |
28
28
  | `assets_intent` | string | `detect` / `none` / free-text | Describes whether assets should be extracted. Values: `detect` (auto-detect from source — default when absent), `none` (skip assets), or a free-text description of expected assets (e.g., "JSON schemas in schemas/"). |
29
29
  | `target_version` | string | Semantic version (`X.Y.Z` or `X.Y.Z-prerelease`) | User-specified target version. When present, overrides auto-detection and becomes the skill's version. Recommended for docs-only skills where auto-detection is unavailable. |
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-analyze-source.
4
+ # Team overrides: _bmad/custom/skf-analyze-source.toml (under {project-root})
5
+ # Personal overrides: _bmad/custom/skf-analyze-source.user.toml (under {project-root})
4
6
 
5
7
  [workflow]
6
8
 
@@ -0,0 +1,116 @@
1
+ ---
2
+ nextStepFile: 'health-check.md'
3
+ outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
4
+ writeSkillBriefProbeOrder:
5
+ - '{project-root}/_bmad/skf/shared/scripts/skf-write-skill-brief.py'
6
+ - '{project-root}/src/shared/scripts/skf-write-skill-brief.py'
7
+ ---
8
+
9
+ <!-- Config: communicate in {communication_language}. -->
10
+
11
+ # Step 1a §0a: Docs-Only Short-Circuit
12
+
13
+ Reached from `step-auto-scope.md` §0c when the target is a documentation URL (not a GitHub repo or local path). It validates the URL, writes a minimal brief and analysis report, emits the result envelope, and chains directly to health-check — the standard auto-scope body (§1 through §11 in `step-auto-scope.md`) never runs for a docs-only target. `{coexistence_suffix}`, `{forge_tier}`, `{user_name}`, `{current_date}`, and the classification set upstream in §0/§0c carry into this file.
14
+
15
+ ## MANDATORY SEQUENCE — §0a
16
+
17
+ ### 1. Validate URL reachability
18
+
19
+ ```bash
20
+ curl -sI --max-time 5 {url}
21
+ ```
22
+
23
+ - On **2xx/3xx** response: URL is reachable. Continue.
24
+ - 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}"`, then the error envelope (shape in `references/headless-contract.md`) with `exit_code: 3`, `halt_reason: "resolution-failure"`, `mode: "auto"`, `source_type: "docs-only"`.
25
+
26
+ ### 2. Derive skill name from URL domain
27
+
28
+ 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`).
29
+
30
+ ### 3. Write analysis report
31
+
32
+ Update {outputFile} with docs-only results. If the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md` (applies equally to the brief write in §4 and the result contract in §6).
33
+
34
+ **Update frontmatter:**
35
+ ```yaml
36
+ stepsCompleted: ['init', 'auto-scope']
37
+ lastStep: 'auto-scope'
38
+ source_type: docs-only
39
+ confirmed_units:
40
+ - name: '{skill_name}'
41
+ shape: 'docs-only'
42
+ confidence: 1.0
43
+ export_count: 0
44
+ package_count: 0
45
+ ```
46
+
47
+ **Append body section:**
48
+ ```markdown
49
+ ## Auto-Scope Analysis
50
+
51
+ **Mode:** auto (docs-only short-circuit)
52
+ **Source Type:** docs-only
53
+ **Documentation URL:** {url}
54
+ **Skill Name:** {skill_name}
55
+ ```
56
+
57
+ ### 4. Write skill brief via canonical writer
58
+
59
+ **Resolve `{writeSkillBriefHelper}`** from `{writeSkillBriefProbeOrder}`; first existing path wins; HALT if neither resolves.
60
+
61
+ Create directory `{forge_data_folder}/{skill_name}/` if it does not exist.
62
+
63
+ Pipe the flat context JSON below into the resolved writer with the `--from-flat` flag:
64
+
65
+ ```json
66
+ {
67
+ "name": "{skill_name}",
68
+ "target_version": null,
69
+ "detected_version": null,
70
+ "source_type": "docs-only",
71
+ "source_repo": "{url}",
72
+ "language": "documentation",
73
+ "description": "Skill created from documentation at {url}",
74
+ "forge_tier": "{forge_tier}",
75
+ "created": "{current_date}",
76
+ "created_by": "{user_name}",
77
+ "scope_type": "docs-only",
78
+ "scope_include": [],
79
+ "scope_exclude": [],
80
+ "scope_notes": "Docs-only skill created from documentation URL",
81
+ "scope_rationale": null,
82
+ "scope_tier_a_include": null,
83
+ "scope_amendments": null,
84
+ "doc_urls": [{"url": "{url}", "label": "Primary Documentation"}],
85
+ "scripts_intent": null,
86
+ "assets_intent": null,
87
+ "source_authority": "community",
88
+ "target_ref": null,
89
+ "source_ref": null,
90
+ "version_resolved": "1.0.0"
91
+ }
92
+ ```
93
+
94
+ ```bash
95
+ echo '<context-json>' | uv run {writeSkillBriefHelper} write --target {forge_data_folder}/{skill_name}/skill-brief.yaml --from-flat
96
+ ```
97
+
98
+ ### 5. Emit success envelope
99
+
100
+ ```
101
+ 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"}
102
+ ```
103
+
104
+ If `{coexistence_suffix}` is non-empty (i.e., [A]longside was selected in §0c), include `"coexistence":"alongside"` in the envelope.
105
+
106
+ The `source_type` field signals downstream consumers (BS) to skip repo-based enrichment.
107
+
108
+ ### 6. Write result contract
109
+
110
+ Write the result contract per `shared/references/output-contract-schema.md`: the per-run record and latest copy, same as `step-auto-scope.md` §10.
111
+
112
+ If `{onCompleteCommand}` is non-empty, invoke it now with `--result-path={result_json_path}`.
113
+
114
+ ### 7. Chain to health check
115
+
116
+ Load, read fully, then execute {nextStepFile} to run the shared workflow health check.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
3
3
  nextStepOptions:
4
+ step 1a: 'step-auto-scope.md'
4
5
  step 2: 'scan-project.md'
5
6
  step 3: 'identify-units.md'
6
7
  step 4: 'map-and-detect.md'
@@ -38,6 +39,7 @@ Load {outputFile} and read frontmatter:
38
39
  - `forge_tier`
39
40
  - `existing_skills`
40
41
  - `confirmed_units`
42
+ - `mode` — `'auto'` when the report was produced by the auto-scope path; absent or any other value means interactive
41
43
 
42
44
  ### 3. Present Progress Summary
43
45
 
@@ -53,7 +55,11 @@ Load {outputFile} and read frontmatter:
53
55
 
54
56
  ### 4. Determine Next Step
55
57
 
56
- Map the last completed step to the next step file:
58
+ **IF the report's `mode` is `'auto'`** (produced by the auto-scope path; reached here because the resume invocation did not carry `[auto]`): an auto analysis is a single idempotent pass, not a resumable interactive chain. Do not use the interactive table below.
59
+ - If `auto-scope` is in `stepsCompleted`, the auto analysis already completed — announce "**This auto analysis is already complete.** Would you like to start a new analysis?" and stop.
60
+ - Otherwise (an auto run interrupted before auto-scope finished), re-enter the auto path: load, read fully, then execute `step-auto-scope.md` (it reads the existing report frontmatter and re-runs cleanly). **STOP HERE.**
61
+
62
+ For interactive reports, map the last completed step to the next step file:
57
63
 
58
64
  | Last Completed | Next Step |
59
65
  |----------------|-----------|
@@ -76,16 +82,5 @@ lastContinued: '{current_date}'
76
82
 
77
83
  "**Resuming from {next_step_name}...**"
78
84
 
79
- #### Menu Handling Logic:
80
-
81
- - After progress is confirmed, immediately load, read entire file, then execute the appropriate step from {nextStepOptions}
82
-
83
- #### EXECUTION RULES:
84
-
85
- - This is an auto-proceed continuation step
86
- - Route directly to the next incomplete step
87
-
88
- ## CRITICAL STEP COMPLETION NOTE
89
-
90
- ONLY WHEN the progress state has been read, summarized to the user, and lastContinued updated will you load the appropriate next step file to resume the workflow.
85
+ Auto-proceed: immediately load, read the entire file, then execute the next incomplete step from {nextStepOptions}.
91
86
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
3
- schemaFile: 'assets/skill-brief-schema.md'
3
+ schemaFile: '{briefSchemaPath}'
4
4
  validateBriefSchemaProbeOrder:
5
5
  - '{project-root}/_bmad/skf/shared/scripts/skf-validate-brief-schema.py'
6
6
  - '{project-root}/src/shared/scripts/skf-validate-brief-schema.py'
@@ -20,7 +20,7 @@ To generate a valid skill-brief.yaml file for each confirmed unit using the sche
20
20
  - Generate only for units in confirmed_units — no extras, no omissions
21
21
  - Do not modify recommendations or re-ask for confirmations
22
22
  - Every generated field must trace back to data collected in steps 02-05
23
- - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing summary is NOT the terminal step
23
+ - Chains to the local health-check step via `{nextStepFile}` after completion — the user-facing summary is not the terminal step
24
24
 
25
25
  ## MANDATORY SEQUENCE
26
26
 
@@ -40,7 +40,7 @@ Mark workflow complete and halt.
40
40
 
41
41
  ### 2. Generate Skill-Brief YAML Per Unit
42
42
 
43
- For EACH unit in `confirmed_units`, construct a skill-brief.yaml using:
43
+ For each unit in `confirmed_units`, construct a skill-brief.yaml using:
44
44
 
45
45
  **Field mapping:**
46
46
 
@@ -74,7 +74,7 @@ YAML
74
74
 
75
75
  The script returns JSON `{valid, errors[], warnings[], halt_reason, brief}` — the same validator and contract `skf-brief-skill` runs at consumption time, so a brief that passes here will not be rejected there for structural reasons. Apply the result:
76
76
 
77
- - **`valid: false`** — the `errors[]` name the offending field (e.g. a `description` mis-indented under `scope:`, which leaves the required top-level `description` absent). Repair the assembled YAML and re-run the helper until `valid: true`. **Never write a brief that has not passed this gate.**
77
+ - **`valid: false`** — the `errors[]` name the offending field (e.g. a `description` mis-indented under `scope:`, which leaves the required top-level `description` absent). Repair the assembled YAML and re-run the helper until `valid: true`. A brief is written only after it passes this gate.
78
78
  - **`valid: true`** — carry any non-empty `warnings[]` into the §4 preview, then proceed.
79
79
 
80
80
  This catches structural YAML errors where they are created, rather than letting them surface downstream as a HALT in `skf-brief-skill`'s ratify path.
@@ -84,13 +84,13 @@ This catches structural YAML errors where they are created, rather than letting
84
84
  1. **Name uniqueness** — no duplicate names within the batch or existing skills
85
85
  2. **Source accessible** — project_path exists
86
86
  3. **Language recognized** — valid programming language identifier
87
- 4. **Include patterns** — at least one glob pattern present (the schema requires the `scope.include` key but does not enforce a non-empty list)
88
- 5. **Forge tier match** — matches forge_tier from config
87
+ 4. **Forge tier match** — matches forge_tier from config
88
+
89
+ (The non-empty `scope.include` constraint — at least one glob pattern for every scope type except `docs-only` — is enforced deterministically by the §3a gate, so it is not re-checked here.)
89
90
 
90
91
  **If any check fails:**
91
92
  - Document the failure with specific field and reason
92
- - Repair (3a structural errors) or present to user for correction (3b semantic issues) before writing
93
- - Do NOT write invalid briefs
93
+ - Repair (3a structural errors) or present to user for correction (3b semantic issues) before writing — an invalid brief is not written
94
94
 
95
95
  ### 4. Present Generation Preview
96
96
 
@@ -113,6 +113,8 @@ This catches structural YAML errors where they are created, rather than letting
113
113
 
114
114
  Wait for explicit user confirmation before writing files.
115
115
 
116
+ **GATE [default: Y]** — If `{headless_mode}` is true: auto-confirm [Y], write all briefs, and log: "headless: auto-write {count} briefs". Do not stall at this gate — it is the deliverable-producing step of a headless/pipeline run.
117
+
116
118
  ### 5. Write Files
117
119
 
118
120
  **IF user confirms (Y):**
@@ -120,7 +122,7 @@ Wait for explicit user confirmation before writing files.
120
122
  For each confirmed brief:
121
123
  1. Create directory `{forge_data_folder}/{unit-name}/` if it does not exist
122
124
  2. Write `skill-brief.yaml` to `{forge_data_folder}/{unit-name}/skill-brief.yaml` — write the exact YAML that passed the §3a schema gate verbatim; do not re-serialize, so the bytes on disk are the bytes that validated
123
- 3. Verify file was written successfully
125
+ 3. Verify the file was written; if the write fails, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`
124
126
 
125
127
  **IF user modifies (M):**
126
128
  - Ask which brief and what to change
@@ -132,7 +134,7 @@ For each confirmed brief:
132
134
  - Skip file writing, proceed to report update
133
135
 
134
136
  **IF user cancels (X):**
135
- - HARD HALT with exit code 6 (`user-cancelled`). Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stderr with `status: "error"`, `halt_reason: "user-cancelled"`, `brief_paths: []`, and `unit_counts` reflecting the confirmed/skipped/maybe state from step 5
137
+ - HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"`, `brief_paths: []`, and `unit_counts` reflecting the confirmed/skipped/maybe state from step 5 (shape in `references/headless-contract.md`)
136
138
 
137
139
  ### 6. Determine Next Workflow Per Unit
138
140
 
@@ -204,7 +206,21 @@ To refine any brief, run the recommended next workflow. To re-analyze with diffe
204
206
 
205
207
  ### 9. Result Contract
206
208
 
207
- 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` (stable path for pipeline consumers — copy, not symlink). Include all generated `skill-brief.yaml` paths in `outputs` and brief counts in `summary`.
209
+ 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` (stable path for pipeline consumers — copy, not symlink). Include all generated `skill-brief.yaml` paths in `outputs` and brief counts in `summary`. If the per-run record cannot be written, HARD HALT with exit code 4 (`write-failed`) per `references/headless-contract.md`.
210
+
211
+ ### 9a. Emit Result Envelope
212
+
213
+ When `{headless_mode}` is true, emit the `SKF_ANALYZE_RESULT_JSON` envelope on **stdout** — the success signal for the interactive path, alongside §9's on-disk record (both are produced):
214
+
215
+ ```
216
+ SKF_ANALYZE_RESULT_JSON: {"status":"success","report_path":"{outputFile_abs_path}","brief_paths":["{brief_path_1}",…,"{brief_path_N}"],"unit_counts":{"confirmed":N,"skipped":N,"maybe":N},"exit_code":0,"halt_reason":null,"mode":"interactive"}
217
+ ```
218
+
219
+ - `report_path` — absolute path to {outputFile}.
220
+ - `brief_paths` — every `skill-brief.yaml` written in §5 (empty array when the user skipped writing with [N]).
221
+ - `unit_counts` — confirmed/skipped counts from step 5 (`maybe` reserved; see `references/headless-contract.md`).
222
+
223
+ When `{headless_mode}` is false (interactive human run), skip this section — there is no pipeline consumer to signal.
208
224
 
209
225
  ### 9b. On-Complete Hook (pipeline integration)
210
226
 
@@ -224,5 +240,5 @@ If `{onCompleteCommand}` is empty, skip this section entirely (default behavior
224
240
 
225
241
  ### 10. Chain to Health Check
226
242
 
227
- ONLY WHEN the briefs have been written (or skipped per user abort), the report updated, the summary presented, the result contract saved, and the on-complete hook invoked (or skipped per empty `{onCompleteCommand}`) will you then load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step — do not stop here even though the summary reads as final.
243
+ After the briefs are written (or skipped per user abort), the report updated, the summary presented, the result contract saved, and the on-complete hook invoked (or skipped per empty `{onCompleteCommand}`), load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step — continue past the summary even though it reads as final.
228
244
 
@@ -0,0 +1,33 @@
1
+ # Headless Result Contract & Exit Codes
2
+
3
+ Canonical headless/pipeline contract for skf-analyze-source. Step files emit the envelope and exit with the codes defined here.
4
+
5
+ ## Result Envelope
6
+
7
+ When `{headless_mode}` is true, step 6 (interactive) or step 1a (auto) emits a single-line JSON envelope on **stdout** before chaining to step 7. Every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`, using the exit code and `halt_reason` named at that HALT — so an automator branches on the failure class without grepping message text.
8
+
9
+ ```
10
+ SKF_ANALYZE_RESULT_JSON: {"status":"success|error|redirect|skipped","report_path":"…|null","brief_paths":["…"],"unit_counts":{"confirmed":N,"skipped":N,"maybe":N},"exit_code":0,"halt_reason":null,"mode":"interactive|auto"}
11
+ ```
12
+
13
+ - `status` — `"success"` on the terminal happy path, `"error"` on any HALT, `"redirect"` when coexistence routes to US (merge), `"skipped"` when the user skips a conflicting target.
14
+ - `halt_reason` — one of `null` (success), `"input-missing"`, `"resolution-failure"`, `"pin-invalid"`, `"write-failed"`, `"user-cancelled"`.
15
+ - `exit_code` — matches the table below.
16
+ - `report_path` — absolute path to the analysis report, or `null` on an early HALT.
17
+ - `brief_paths` — array of absolute paths to every generated `skill-brief.yaml` (empty array if none).
18
+ - `unit_counts` — `confirmed` (units approved for briefs) and `skipped` (rejected in step 5, or the skipped target in coexistence §0c) counts; auto mode reports `confirmed:N, skipped:0`. `maybe` is a reserved slot, currently always `0`.
19
+ - `mode` — `"auto"` when the `[auto]` flag was active, `"interactive"` otherwise.
20
+
21
+ In auto mode the envelope also carries `coexistence` (the §0c decision) and, when a pin resolves, `pinned_ref`/`pinned_version` — `step-auto-scope.md` §0b/§0c define those field semantics. The docs-only path adds `source_type: "docs-only"`.
22
+
23
+ ## Exit Codes
24
+
25
+ Every HARD HALT exits with a stable code so headless automators can branch on the failure class.
26
+
27
+ | Code | Meaning | Raised by |
28
+ | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
29
+ | 0 | success / skipped / redirect | step 7 (terminal — health check completion); coexistence `"skipped"`/`"redirect"` statuses from §0c |
30
+ | 2 | input-missing | On Activation (config.yaml not loadable); step 1 §3 (project path empty/invalid in headless mode); step 1 §2b (auto mode without `--project-path`) |
31
+ | 3 | resolution-failure | step 1 §2 (`forge-tier.yaml` missing at `{sidecar_path}/forge-tier.yaml`); step 1 §3 (project path does not exist or remote URL inaccessible); step 1a §0a (docs-only URL unreachable); step 1a §0b (`halt_reason: "pin-invalid"` when the supplied `--pin` matches no tag or branch); step 1a §3 (shape detection script error, exit code 2) |
32
+ | 4 | write-failed | a failed write of the analysis report (step 1 §6 / step 1a §7 / §0a), a `skill-brief.yaml` (step 6 §5 / step 1a §8 / §0a), or the result contract (step 6 §9 / step 1a §10 / §0a) |
33
+ | 6 | user-cancelled | any interactive menu in steps 2/3/5/6 (user selected `[X]` Cancel and exit) |
@@ -16,8 +16,7 @@ Chain to the shared workflow self-improvement health check at `{nextStepFile}`.
16
16
  ## Rules
17
17
 
18
18
  - No user-facing reports, file writes, or result contracts in this step — those belong in step 6
19
- - Delegate directly to `{nextStepFile}` with no additional commentary
20
- - Do not attempt any other action between loading this step and executing `{nextStepFile}`
19
+ - Delegate directly to `{nextStepFile}` with no other action or commentary in between
21
20
 
22
21
  ## MANDATORY SEQUENCE
23
22
 
@@ -1,10 +1,13 @@
1
1
  ---
2
2
  nextStepFile: 'map-and-detect.md'
3
3
  outputFile: '{forge_data_folder}/analyze-source-report-{project_name}.md'
4
- heuristicsFile: 'references/unit-detection-heuristics.md'
4
+ heuristicsFile: '{unitDetectionHeuristicsPath}'
5
5
  disqualifyCandidatesProbeOrder:
6
6
  - '{project-root}/_bmad/skf/shared/scripts/skf-disqualify-candidates.py'
7
7
  - '{project-root}/src/shared/scripts/skf-disqualify-candidates.py'
8
+ detectLanguageProbeOrder:
9
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-language.py'
10
+ - '{project-root}/src/shared/scripts/skf-detect-language.py'
8
11
  ---
9
12
 
10
13
  <!-- Config: communicate in {communication_language}. -->
@@ -36,24 +39,13 @@ Load {heuristicsFile} for classification rules.
36
39
 
37
40
  **Resolve `{disqualifyCandidatesHelper}`** from `{disqualifyCandidatesProbeOrder}`; first existing path wins. HALT if no candidate exists.
38
41
 
39
- For EACH detected boundary from the scan:
42
+ For each detected boundary from the scan, apply the classification rules from {heuristicsFile} (loaded in §1):
40
43
 
41
- **Step A — Count detection signals:**
42
- - Check strong signals (independent manifest, separate entry point, Docker config, distinct export surface, workspace member)
43
- - Check moderate signals (directory depth, naming convention, separate tests, README, CI/CD reference)
44
- - Check weak signals (large directory, comment boundaries, import clustering)
44
+ **Step A — Count detection signals:** tally the Strong / Moderate / Weak signals per its Detection Signals tables.
45
45
 
46
- **Step B — Classify boundary type:**
47
- - Service Boundary — independent deployable unit
48
- - Package Boundary — workspace member or independently versioned
49
- - Module Boundary — logical grouping within a package
50
- - Library Boundary — third-party with significant project-specific usage
51
- - Composite Boundary — ≥2 boundaries that only deliver value together (detected in §3b below; not assigned during initial per-boundary classification)
46
+ **Step B — Classify boundary type** per its Boundary Classification section. (Composite is detected separately in §3b below — not during this initial per-boundary pass.)
52
47
 
53
- **Step C — Assign scope type:**
54
- - `full-library` — entire codebase of the unit
55
- - `specific-modules` — selected components or packages
56
- - `public-api` — only exported interfaces
48
+ **Step C — Assign scope type** from that same section for the boundary's type.
57
49
 
58
50
  **Step D — Run deterministic disqualification filter (script):**
59
51
 
@@ -87,9 +79,9 @@ Run the shared disqualification helper to apply the deterministic subset of the
87
79
  - **Test-only** — test utilities with no production code
88
80
  - **Already skilled** — exists in `existing_skills` list (recommend `update-skill` instead)
89
81
 
90
- Remove any boundary that fails one of these LLM-judged rules from the working `kept` set and append it to `dropped[]` with the appropriate reason. Reasons recorded by the script (`too-few-files`, `too-low-loc`, `generated-code`, `auto-generated-tag`) are authoritative; do NOT re-evaluate those rules manually.
82
+ Remove any boundary that fails one of these LLM-judged rules from the working `kept` set and append it to `dropped[]` with the appropriate reason. Reasons recorded by the script (`too-few-files`, `too-low-loc`, `generated-code`, `auto-generated-tag`) are authoritative do not re-evaluate those rules manually.
91
83
 
92
- **Qualification CONFIRMATION:** Visually skim the script's `kept`/`dropped` decisions for sanity (e.g., a boundary you expected to qualify that landed in `dropped` — surface the script's `reason` and `context.first_match` to the user in §5 so they can override if the heuristic was wrong for this project).
84
+ **Qualification check:** Visually skim the script's `kept`/`dropped` decisions for sanity (e.g., a boundary you expected to qualify that landed in `dropped` — surface the script's `reason` and `context.first_match` to the user in §5 so they can override if the heuristic was wrong for this project).
93
85
 
94
86
  ### 3. Build Unit Classification Table
95
87
 
@@ -110,9 +102,7 @@ For disqualified candidates, note reason:
110
102
 
111
103
  After building the classification table, apply the Composite Boundary detection heuristic from {heuristicsFile} against the qualifying units:
112
104
 
113
- 1. **Scan for merge candidates:** Among the qualifying units (from `kept[]`), find groups of ≥2 Package or Module boundaries that meet EITHER trigger:
114
- - **Mutual hard dependency:** Every constituent imports from at least one other constituent in the group, AND no constituent's public API is self-contained
115
- - **Shared integration surface:** Constituents share types/traits defined in one constituent but consumed by all others, AND the consuming constituents have no independent barrel
105
+ 1. **Scan for merge candidates:** Among the qualifying units (from `kept[]`), find groups of ≥2 Package or Module boundaries that meet either Composite trigger — **Mutual hard dependency** or **Shared integration surface** — as defined in {heuristicsFile}'s Composite Boundary heuristic.
116
106
 
117
107
  2. **If candidate groups are found**, propose each merge:
118
108
  - Derive a composite name from the common namespace prefix or repo name
@@ -121,16 +111,23 @@ After building the classification table, apply the Composite Boundary detection
121
111
 
122
112
  3. **If no candidate groups are found**, skip to §4.
123
113
 
124
- **Merge does NOT fire for:** Units already flagged as Stack Skill Candidates in step 4 (map-and-detect §5) — those are multi-unit groupings that deliver value *separately* but are *also* useful together. Composite merges are for units that are *only* useful together (the key distinction). If a group of units is independently useful but commonly combined, it remains as separate units and is flagged as a stack skill candidate later.
114
+ **Merge does not fire for:** Units already flagged as Stack Skill Candidates in step 4 (map-and-detect §5) — those are multi-unit groupings that deliver value *separately* but are *also* useful together. Composite merges are for units that are *only* useful together (the key distinction). If a group of units is independently useful but commonly combined, it remains as separate units and is flagged as a stack skill candidate later.
125
115
 
126
116
  **This step is a recommendation — not automatic.** Merges are presented to the user in §5 for confirmation (see "Composite Merge Proposals" below). If the user rejects a merge, the constituents remain as separate units in the classification table.
127
117
 
128
118
  ### 4. Detect Primary Language Per Unit
129
119
 
130
- For each qualifying unit (including any approved composites from §3b), determine the primary programming language based on:
131
- - File extensions in the unit directory
132
- - Manifest file type (package.json JS/TS, Cargo.toml → Rust, go.mod → Go, etc.)
133
- - Entry point file extension
120
+ For each qualifying unit (including any approved composites from §3b), detect the primary language deterministically via the shared helper — the single source of truth for the manifest→language rule table (no in-prose restatement, which drifts from the script's tsconfig JS-vs-TS and `build.gradle` Java-vs-Kotlin disambiguation).
121
+
122
+ **Resolve `{detectLanguageHelper}`** from `{detectLanguageProbeOrder}`; first existing path wins.
123
+
124
+ For each unit, pipe its file list — the `files` array built for that boundary in the §2 boundaries JSON — as the tree:
125
+
126
+ ```bash
127
+ echo '{"tree": [<unit files — forward-slash, repo-relative>]}' | uv run {detectLanguageHelper}
128
+ ```
129
+
130
+ Read `.language` and `.confidence` for the unit. When confidence is low (the extension-frequency fallback fired — no manifest matched), surface it in §5 so the user can override the guess.
134
131
 
135
132
  ### 5. Present Classifications
136
133
 
@@ -195,16 +192,8 @@ Display: "**Select:** [C] Continue to Export Mapping and Integration Detection |
195
192
  #### Menu Handling Logic:
196
193
 
197
194
  - IF C: Save classifications to {outputFile}, update frontmatter, then load, read entire file, then execute {nextStepFile}
198
- - IF X: HARD HALT with exit code 6 (`user-cancelled`). Emit the `SKF_ANALYZE_RESULT_JSON` envelope on stderr with `status: "error"`, `halt_reason: "user-cancelled"`, and counts/paths reflecting state at cancellation
195
+ - IF X: HARD HALT with exit code 6 (`user-cancelled`). Emit the error envelope on stderr with `halt_reason: "user-cancelled"` and counts/paths reflecting state at cancellation (shape in `references/headless-contract.md`)
199
196
  - IF Any other: help user, then [Redisplay Menu Options](#7-present-menu-options)
200
197
 
201
- #### EXECUTION RULES:
202
-
203
- - ALWAYS halt and wait for user input after presenting menu
204
- - **GATE [default: C]** — If `{headless_mode}`: accept all classifications and auto-proceed, log: "headless: auto-accept unit classifications"
205
- - ONLY proceed to next step when user selects 'C'
206
-
207
- ## CRITICAL STEP COMPLETION NOTE
208
-
209
- ONLY WHEN the Identified Units section has been appended to {outputFile} with complete classification tables, disqualification records, and language detection results, and frontmatter stepsCompleted has been updated, will you load and read fully {nextStepFile} to begin export mapping and integration detection.
198
+ **GATE [default: C]** — present the menu and wait for the user's choice. If `{headless_mode}`: accept all classifications and auto-proceed, log: "headless: auto-accept unit classifications".
210
199