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
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # ///
5
+ """Deterministic NEW_FILE detection for skf-update-skill (detect-changes Category D).
6
+
7
+ `skf-hash-content compare` classifies files already tracked in the provenance
8
+ map (UNCHANGED / MODIFIED_FILE / DELETED_FILE) but by design cannot report a
9
+ file that is present in source yet absent from the provenance map — a NEW_FILE.
10
+ Deriving that set is a set-difference plus a filter with exactly one correct
11
+ answer per input: take every `source_file` in the scripts/assets inventory
12
+ (emitted by `skf-detect-scripts-assets detect`), subtract the paths already in
13
+ `file_entries[].source_file`, and set aside any user-authored `[MANUAL]` path.
14
+ Doing that subtraction in the prompt drifts across runs; doing it here is
15
+ byte-stable.
16
+
17
+ Usage:
18
+ skf-detect-scripts-assets.py detect <source-root> \
19
+ | skf-new-file-diff.py <provenance-map-path>
20
+
21
+ Reads the detect JSON on stdin (needs `scripts_inventory[]` and
22
+ `assets_inventory[]`, each row carrying `source_file`). Reads the provenance
23
+ map at the path argument (canonical `{file_entries: [...]}` object, or a bare
24
+ array of entries).
25
+
26
+ Output JSON (stdout):
27
+ {
28
+ "new_files": [ {"source_file": "...", "kind": "script|asset"}, ... ],
29
+ "skipped_manual": [ "...", ... ], # under scripts/[MANUAL]/ or assets/[MANUAL]/
30
+ "already_tracked": [ "...", ... ], # present in file_entries
31
+ "stats": {"inventory_total": N, "new": N,
32
+ "skipped_manual": N, "already_tracked": N}
33
+ }
34
+
35
+ All three arrays are sorted by source_file; new_files carries the kind of the
36
+ inventory it came from (scripts -> "script", assets -> "asset"). A path found
37
+ in both inventories is counted once, resolved as "script".
38
+
39
+ Exit codes:
40
+ 0 success
41
+ 2 bad input (unreadable/invalid stdin JSON, missing/invalid provenance map)
42
+ """
43
+
44
+ import json
45
+ import re
46
+ import sys
47
+ from pathlib import Path
48
+
49
+ # scripts/[MANUAL]/... or assets/[MANUAL]/... anywhere in the (posix) path
50
+ _MANUAL_RE = re.compile(r"(?:^|/)(?:scripts|assets)/\[MANUAL\]/")
51
+
52
+
53
+ def _fail(msg: str) -> "NoReturn": # type: ignore[valid-type]
54
+ print(json.dumps({"error": msg}), file=sys.stderr)
55
+ sys.exit(2)
56
+
57
+
58
+ def _posix(p: str) -> str:
59
+ return p.replace("\\", "/")
60
+
61
+
62
+ def load_tracked_source_files(provenance_path: Path) -> set[str]:
63
+ """Return the set of file_entries[].source_file already in the provenance map."""
64
+ try:
65
+ data = json.loads(provenance_path.read_text(encoding="utf-8"))
66
+ except (json.JSONDecodeError, OSError) as exc:
67
+ _fail(f"failed to read provenance map {provenance_path}: {exc}")
68
+
69
+ if isinstance(data, list):
70
+ entries = data
71
+ elif isinstance(data, dict):
72
+ entries = data.get("file_entries")
73
+ if entries is None:
74
+ _fail(f"provenance map {provenance_path} has no `file_entries` field")
75
+ if not isinstance(entries, list):
76
+ _fail(f"`file_entries` in {provenance_path} is not an array")
77
+ else:
78
+ _fail(
79
+ f"provenance map {provenance_path} must be an object or array; "
80
+ f"got {type(data).__name__}"
81
+ )
82
+
83
+ tracked: set[str] = set()
84
+ for entry in entries:
85
+ if isinstance(entry, dict):
86
+ sf = entry.get("source_file")
87
+ if isinstance(sf, str) and sf:
88
+ tracked.add(_posix(sf))
89
+ return tracked
90
+
91
+
92
+ def collect_inventory(detect: dict) -> list[tuple[str, str]]:
93
+ """Return [(source_file, kind), ...] from the detect JSON, de-duplicated.
94
+
95
+ Scripts win over assets when a path appears in both, so kind is stable.
96
+ """
97
+ seen: dict[str, str] = {}
98
+ for key, kind in (("scripts_inventory", "script"), ("assets_inventory", "asset")):
99
+ rows = detect.get(key)
100
+ if rows is None:
101
+ continue
102
+ if not isinstance(rows, list):
103
+ _fail(f"`{key}` in detect JSON is not an array")
104
+ for row in rows:
105
+ if not isinstance(row, dict):
106
+ _fail(f"`{key}` entry is not an object: {row!r}")
107
+ sf = row.get("source_file")
108
+ if not isinstance(sf, str) or not sf:
109
+ _fail(f"`{key}` entry missing string `source_file`: {row!r}")
110
+ sf = _posix(sf)
111
+ seen.setdefault(sf, kind) # first inventory wins the kind
112
+ return sorted(seen.items())
113
+
114
+
115
+ def diff(detect: dict, tracked: set[str]) -> dict:
116
+ new_files: list[dict] = []
117
+ skipped_manual: list[str] = []
118
+ already_tracked: list[str] = []
119
+
120
+ inventory = collect_inventory(detect)
121
+ for sf, kind in inventory:
122
+ if sf in tracked:
123
+ already_tracked.append(sf)
124
+ elif _MANUAL_RE.search(sf):
125
+ skipped_manual.append(sf)
126
+ else:
127
+ new_files.append({"source_file": sf, "kind": kind})
128
+
129
+ return {
130
+ "new_files": new_files,
131
+ "skipped_manual": sorted(skipped_manual),
132
+ "already_tracked": sorted(already_tracked),
133
+ "stats": {
134
+ "inventory_total": len(inventory),
135
+ "new": len(new_files),
136
+ "skipped_manual": len(skipped_manual),
137
+ "already_tracked": len(already_tracked),
138
+ },
139
+ }
140
+
141
+
142
+ def main(argv: list[str]) -> int:
143
+ if len(argv) != 2:
144
+ _fail("usage: skf-new-file-diff.py <provenance-map-path> (detect JSON on stdin)")
145
+
146
+ provenance_path = Path(argv[1])
147
+ if not provenance_path.exists():
148
+ _fail(f"provenance map not found: {provenance_path}")
149
+
150
+ raw = sys.stdin.read()
151
+ if not raw.strip():
152
+ _fail("no detect JSON on stdin")
153
+ try:
154
+ detect = json.loads(raw)
155
+ except json.JSONDecodeError as exc:
156
+ _fail(f"invalid detect JSON on stdin: {exc}")
157
+ if not isinstance(detect, dict):
158
+ _fail(f"detect JSON must be an object; got {type(detect).__name__}")
159
+
160
+ tracked = load_tracked_source_files(provenance_path)
161
+ result = diff(detect, tracked)
162
+ print(json.dumps(result, indent=2))
163
+ return 0
164
+
165
+
166
+ if __name__ == "__main__":
167
+ sys.exit(main(sys.argv))
@@ -7,9 +7,9 @@ description: Pre-code stack feasibility verification against architecture and PR
7
7
 
8
8
  ## Overview
9
9
 
10
- Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping. This is a read-only workflow — it never modifies skills or input documents, only reads and produces a feasibility report. Every verdict must cite specific APIs, types, or function signatures from the generated skills.
10
+ Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping. Read-only: it reads skills and input documents and writes only the feasibility report (see Workflow Rules).
11
11
 
12
- **Schema contract:** This skill is the PRODUCER of the feasibility report schema defined under the SKF shared references (`_bmad/skf/shared/references/feasibility-report-schema.md` in installed mode; `src/shared/references/feasibility-report-schema.md` in a dev checkout). All report outputs emit `schemaVersion: "1.0"` in frontmatter, use only the defined verdict tokens (`Verified|Plausible|Risky|Blocked` per pair; `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE` overall), follow the fixed section-heading order, and are written through `src/shared/scripts/skf-atomic-write.py write` to both the timestamped file and the stable `-latest.md` copy.
12
+ **Schema contract:** This skill is the producer of the SKF shared feasibility report schema every report conforms to it.
13
13
 
14
14
  ## Conventions
15
15
 
@@ -53,34 +53,20 @@ These rules apply to every step in this workflow:
53
53
  |--------|--------|
54
54
  | **Inputs** | architecture_doc_path [required], prd_path [optional], previous_report_path [optional] |
55
55
  | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--architecture-doc <path>` (skip step 1 prompt for the required input); `--prd <path>` (skip step 1 prompt for the optional PRD); `--previous-report <path>` (skip step 1 prompt for delta comparison) |
56
- | **Gates** | step 1: Input Gate [use args] | step 6: Confirm Gate [C] |
56
+ | **Gates** | step 1 Input Gate (use args); step 6 Report Menu ([R] review / [X] exit, headless default X). Steps 2-3 also hold elective vacuous-analysis guards (0% coverage, all-Blocked) that only fire in degenerate cases; every guard auto-resolves to Continue in headless. |
57
57
  | **Outputs** | `feasibility-report-{projectSlug}-{timestamp}.md` and `feasibility-report-{projectSlug}-latest.md` (copy, not symlink) per the SKF shared feasibility report schema (`_bmad/skf/shared/references/feasibility-report-schema.md`; `src/shared/references/…` in a dev checkout) — with integration verdicts, coverage analysis, recommendations, and evidence sources; plus `verify-stack-result-{timestamp}.json` and `verify-stack-result-latest.json` |
58
58
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Per-flag args (`--architecture-doc`, `--prd`, `--previous-report`) consumed at the gates that would otherwise prompt. |
59
- | **Exit codes** | See "Exit Codes" below |
60
-
61
- ## Exit Codes
62
-
63
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
64
-
65
- | Code | Meaning | Raised by |
66
- | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
67
- | 0 | success | step 7 (terminal) |
68
- | 2 | input-missing / input-invalid | step 1 §1 (headless missing `architecture-doc` arg, or invalid path) → `input-missing`; non-existent file → `input-invalid` |
69
- | 3 | resolution-failure | step 1 §2 (`{skills_output_folder}` does not exist or is empty); step 1 §3 (forge_data_folder unconfigured) |
70
- | 4 | write-failure | On-Activation §3 pre-flight write probe; step 1 §4 (atomic write of report skeleton failed); step 6 §4b (result-contract write failed) |
71
- | 5 | state-conflict | step 1 §3 (fewer than 2 valid skills found — stack requires ≥2); step 1 §1 (`previousReport` resolves to same inode as `{outputFile}`); step 6 §1 (report section order or schemaVersion mismatch — schema-violation) |
72
- | 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 6 menu cancelled |
73
- | 7 | inventory-unreliable | step 1 §2 (>20% subagent failures or enumerate-stack-skills warnings exceed budget) |
59
+ | **Exit codes** | See `references/exit-codes.md` |
74
60
 
75
61
  ## Result Contract (Headless)
76
62
 
77
- When `{headless_mode}` is true, step 6 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"`:
63
+ When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every headless hard halt emits the same envelope shape on **stderr** with `status: "error"`:
78
64
 
79
65
  ```
80
66
  SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null","report_latest_path":"…|null","overall_verdict":"…|null","coverage_percentage":0,"recommendation_count":0,"exit_code":0,"halt_reason":null}
81
67
  ```
82
68
 
83
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"skills-folder-missing"`, `"insufficient-skills"`, `"forge-folder-unconfigured"`, `"previous-report-collision"`, `"inventory-unreliable"`, `"schema-violation"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above. `overall_verdict` uses the schema tokens (`FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`).
69
+ `status` is `"success"` on the terminal happy path, `"error"` on any halt. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"skills-folder-missing"`, `"insufficient-skills"`, `"forge-folder-unconfigured"`, `"resolution-failure"`, `"previous-report-collision"`, `"inventory-unreliable"`, `"schema-violation"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches `references/exit-codes.md` (the `analysis-halted` / exit-8 gates are interactive-only, so they never reach this envelope). `overall_verdict` uses the schema tokens (`FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`).
84
70
 
85
71
  ## On Activation
86
72
 
@@ -91,7 +77,7 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
91
77
  2. **Compute run-scoped variables** (same place as config so every stage can reference them without re-derivation):
92
78
  - `project_slug` ← slugify `project_name` (lowercase, hyphens only, no unicode, no whitespace)
93
79
  - `timestamp` ← UTC `YYYYMMDD-HHmmss` captured at activation time
94
- - These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently. (Computing them here resolves an order-of-operations bug where the §1 §3 inode-collision check referenced `{outputFile}` before `{project_slug}` and `{timestamp}` were defined.)
80
+ - These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently.
95
81
 
96
82
  3. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
97
83
 
@@ -119,6 +105,10 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
119
105
 
120
106
  Stash all four as workflow-context variables. Stage files reference them directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default.
121
107
 
108
+ The same merge resolves `workflow.on_complete` (default empty = no-op); report.md §5 executes it, if non-empty, at the terminal stage.
109
+
110
+ Also apply the array surfaces so they are not silent no-ops: execute each entry in `workflow.activation_steps_prepend` in order now; treat every entry in `workflow.persistent_facts` as standing context for the whole run (`file:`-prefixed entries load their file/glob contents as facts — the bundled default glob is `{project-root}/**/project-context.md`); then execute each entry in `workflow.activation_steps_append` after activation completes.
111
+
122
112
  5. **Pre-flight write probe.** Verify `{outputFolderPath}` is writable. A read-only mount, full disk, or permissions-denied path otherwise only surfaces at init.md §4 atomic write — by then the user has already gone through the input prompts:
123
113
 
124
114
  ```bash
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-verify-stack.
4
+ # Team overrides: _bmad/custom/skf-verify-stack.toml under {project-root}
5
+ # Personal overrides: _bmad/custom/skf-verify-stack.user.toml under {project-root}
4
6
 
5
7
  [workflow]
6
8
 
@@ -34,6 +36,12 @@ persistent_facts = [
34
36
  "file:{project-root}/**/project-context.md",
35
37
  ]
36
38
 
39
+ # Instruction executed when the workflow reaches its terminal stage (after the
40
+ # feasibility report + result JSON are written). Empty = no-op. Use to route
41
+ # the verdict onward or trigger a downstream step without forking the skill.
42
+
43
+ on_complete = ""
44
+
37
45
  # --- Optional asset overrides ---
38
46
  #
39
47
  # Lift the canonical asset paths so orgs can substitute house-style copies
@@ -43,8 +51,9 @@ report_template_path = ""
43
51
  integration_rules_path = ""
44
52
  coverage_patterns_path = ""
45
53
 
46
- # Override the destination directory for the feasibility report. Empty =
47
- # use {forge_data_folder} from config.yaml. Useful for orgs that want
48
- # verification artifacts to land outside the per-skill forge tree.
54
+ # Override the destination directory for the feasibility report only. Empty =
55
+ # use {forge_data_folder} from config.yaml. The result-contract JSON
56
+ # (verify-stack-result-*.json) always stays in {forge_data_folder} as a stable
57
+ # path for pipeline consumers, so redirecting this does not relocate it.
49
58
 
50
59
  output_folder_path = ""
@@ -22,32 +22,4 @@ Parse document section headers for technology groupings:
22
22
  - `## Backend Core` → technologies in backend layer
23
23
  - `## AI Layer` → AI-related technologies
24
24
 
25
- **Mermaid Diagram Handling:** Do NOT parse Mermaid diagram syntax (`graph`, `flowchart`, `sequenceDiagram`, etc.) for technology detection. Only use prose text (headings, paragraphs, lists, tables) for detection. If the architecture document appears to list technologies exclusively inside Mermaid diagrams, note this in the coverage results as a detection limitation and recommend the user add prose-based technology listings.
26
-
27
- ### Coverage Verdict
28
-
29
- The coverage matrix renders two verdict tokens per referenced technology: **Covered** and **Missing**. Extra skills (skills present in the inventory but NOT referenced by the architecture document) are tracked in a separate informational subdivision — they are not coverage verdicts and do not appear in the primary `Technology → Skill Match → Verdict` column. See step 2 §4 for the Extra-skill subdivision output.
30
-
31
- | Verdict | Meaning |
32
- |-------------|------------------------------------------------------------------|
33
- | **Covered** | A generated skill exists in `skills/` for this technology |
34
- | **Missing** | Technology is referenced in architecture doc but no skill exists |
35
-
36
- **Informational — Extra skills subdivision (not a verdict; rendered in a separate section per step 2 §4):**
37
-
38
- | Label | Meaning |
39
- |----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
40
- | **Extra (unreferenced)** | Skill exists with a resolvable `source_repo` / `source_root`, but no architecture tech token matches it |
41
- | **Orphan (source_repo unresolvable)** | Skill's `source_repo` is empty, malformed, or its basename cannot be extracted — cross-reference against architecture impossible |
42
-
43
- ## Output Format
44
-
45
- When presenting coverage results, use the following structure:
46
-
47
- ```
48
- | Technology | Source Section | Skill Match | Verdict |
49
- |------------|---------------|-------------|---------|
50
- | {tech_name} | {section_heading} | {skill_name or '—'} | Covered / Missing |
51
- ```
52
-
53
- Include a summary line: `Coverage: {covered_count}/{total_count} ({percentage}%)`
25
+ **Mermaid Diagram Handling:** Do not parse Mermaid diagram syntax (`graph`, `flowchart`, `sequenceDiagram`, etc.) for technology detection use only prose text (headings, paragraphs, lists, tables). If the architecture document appears to list technologies exclusively inside Mermaid diagrams, note this in the coverage results as a detection limitation and recommend the user add prose-based technology listings.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  nextStepFile: 'integrations.md'
3
3
  coveragePatternsData: '{coveragePatternsPath}'
4
+ coverageTallyScript: 'scripts/skf-coverage-tally.py'
4
5
  feasibilitySchemaProbeOrder:
5
6
  - '{project-root}/_bmad/skf/shared/references/feasibility-report-schema.md'
6
7
  - '{project-root}/src/shared/references/feasibility-report-schema.md'
@@ -66,7 +67,7 @@ For each referenced technology in the list:
66
67
  4. Compare the resulting basenames/segments against the tech tokens via case-insensitive equality (no substring/fuzzy matching)
67
68
  5. A match on either `source_repo` basename or `source_root` last segment counts as a hit
68
69
 
69
- **Detect a deliberate-removal signal (from the architecture document):** Before assigning Covered/Missing, check whether the referenced technology is explicitly marked for removal or replacement in the architecture document itself. Be conservative — recognize a removal signal ONLY when one of these is present, and when in doubt do NOT treat it as removal:
70
+ **Detect a deliberate-removal signal (from the architecture document):** Before assigning Covered/Missing, check whether the referenced technology is explicitly marked for removal or replacement in the architecture document itself. Be conservative — recognize a removal signal only when one of these is present, and when in doubt leave it as a normal reference (a false removal signal silently drops a real coverage gap):
70
71
  - The technology is listed under a section whose heading matches (case-insensitive) one of: "deprecated", "removed", "legacy", "migrating away", "being replaced", "to be removed", "sunset", "retiring".
71
72
  - The technology's own mention carries an inline removal annotation, e.g. "(deprecated)", "(being replaced by …)", "(removing)", "(to be removed)", "(legacy)".
72
73
 
@@ -77,13 +78,19 @@ Record the cited section heading or annotation text as evidence for every techno
77
78
  - **Replaced** — no matching skill exists AND a deliberate-removal signal (above) was found; the technology is intentionally being removed/replaced, so no skill should exist for it
78
79
  - **Missing** — no matching skill found and no removal signal
79
80
 
80
- **Replaced** technologies are EXCLUDED from the coverage denominator — they are not a gap to close. The denominator (`live_count`) is the count of referenced technologies that are **Covered** or **Missing**; **Replaced** technologies do not count toward it.
81
-
82
81
  Build the coverage matrix as a structured table.
83
82
 
83
+ **Tally the matrix deterministically.** Assigning each verdict is judgment; counting the classes and computing the percentage has one correct answer, so delegate it. Serialize the matrix as `{"rows": [{"technology": "…", "verdict": "Covered|Missing|Replaced"}, …]}` and run:
84
+
85
+ ```bash
86
+ echo '<rows JSON>' | uv run {coverageTallyScript} --stdin
87
+ ```
88
+
89
+ The script (run `uv run {coverageTallyScript} --help` for the contract) returns `covered_count`, `missing_count`, `replaced_count`, `live_count` (the denominator — Covered + Missing, with Replaced excluded because a technology being removed is not a gap to close), `total_referenced`, and `coverage_percentage` (Replaced excluded from the denominator, half-up rounding pinned so the same matrix always yields the same integer). Consume these values in §5 and §6 rather than recomputing them. If `uv` is unavailable (e.g. claude.ai web), compute the same values inline per the `--help` contract: `live_count = covered + missing`; `coverage_percentage = round-half-up(covered / live_count * 100)`, or `0` when `live_count` is `0`.
90
+
84
91
  ### 4. Detect Extra Skills
85
92
 
86
- Check if any skills in the inventory are NOT referenced in the architecture document.
93
+ Check if any skills in the inventory are not referenced in the architecture document.
87
94
 
88
95
  **Subdivide into two categories (both informational — not errors):**
89
96
  - **Extra (unreferenced)** — The skill's `source_repo` / `source_root` resolves cleanly (both non-empty and well-formed), but no architecture document tech token matches it.
@@ -103,7 +110,7 @@ Extra and Orphan skills are informational only. They do not affect the coverage
103
110
  |------------|---------------|-------------|---------|
104
111
  | {tech_name} | {section_heading} | {skill_name or '—'} | {Covered / Missing / Replaced} |
105
112
 
106
- **Coverage: {covered_count}/{live_count} ({percentage}%)** `live_count` excludes technologies marked **Replaced** (being removed/replaced); they are not a coverage gap.
113
+ **Coverage: {covered_count}/{live_count} ({coverage_percentage}%)** (from the §3 tally; `live_count` excludes **Replaced** technologies)
107
114
 
108
115
  {IF 100% coverage AND no Extra skills:}
109
116
  **All referenced technologies have a matching skill. No extra skills detected.**
@@ -125,7 +132,7 @@ Extra and Orphan skills are informational only. They do not affect the coverage
125
132
 
126
133
  ### 6. Append to Report
127
134
 
128
- **Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. HALT if no candidate exists.
135
+ **Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
129
136
 
130
137
  **Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback).
131
138
 
@@ -133,9 +140,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
133
140
  - Include the full coverage table
134
141
  - Include coverage percentage
135
142
  - Include missing skill recommendations
136
- - Include the Replaced (being removed/replaced) subdivision from section 3, with the cited removal evidence — these are NOT gaps and carry no [CS]/[QS] recommendation
143
+ - Include the Replaced (being removed/replaced) subdivision from section 3, with the cited removal evidence — these are not gaps and carry no [CS]/[QS] recommendation
137
144
  - Include the Extra (unreferenced) and Orphan (source_repo unresolvable) subdivisions from section 4
138
- - Update frontmatter: append `'coverage'` to `stepsCompleted`; set `coveragePercentage` (integer 0..100) computed as `covered_count / live_count` (Replaced technologies excluded from the denominator; if `live_count` is 0, set `coveragePercentage: 0`)
145
+ - Update frontmatter: append `'coverage'` to `stepsCompleted`; set `coveragePercentage` to the `coverage_percentage` value from the §3 tally (integer 0..100)
139
146
  - Pipe the updated full content through `python3 {atomicWriteHelper} write --target {outputFile}` and again with `--target {outputFileLatest}`
140
147
 
141
148
  ### 7. Auto-Proceed to Next Step
@@ -147,7 +154,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
147
154
 
148
155
  **Select:** [X] Halt workflow (recommended) | [C] Continue anyway"
149
156
 
150
- - IF X: "**Workflow halted.** Update the architecture document and re-run [VS] when ready."END workflow
157
+ **GATE [default: C]** — Interactive-only guard. If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: continuing past all-Replaced coverage gate (nothing live to verify)". Headless never takes [X], so step 6 still emits the result contract the run resolves to `NOT_FEASIBLE` via the synthesize zero-coverage short-circuit.
158
+
159
+ - IF X: "**Workflow halted.** Coverage Analysis saved to `{outputFile}`. Update the architecture document and re-run [VS] when ready." HALT (exit code 8, `halt_reason: "analysis-halted"`).
151
160
  - IF C: "**Continuing — the analysis covers only technologies marked for removal and will be limited.**" Load, read the full file and then execute `{nextStepFile}`.
152
161
 
153
162
  {IF coveragePercentage is 0% AND live_count > 0:}
@@ -157,7 +166,9 @@ Write the **Coverage Analysis** section to `{outputFile}` (see `{feasibilitySche
157
166
 
158
167
  **Select:** [X] Halt workflow (recommended) | [C] Continue anyway"
159
168
 
160
- - IF X: "**Workflow halted.** Generate skills and re-run [VS] when ready." — END workflow
169
+ **GATE [default: C]** Interactive-only guard. If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: continuing past 0%-coverage gate". Headless never takes [X], so step 6 still emits the result contract the run resolves to `NOT_FEASIBLE` via the synthesize zero-coverage short-circuit.
170
+
171
+ - IF X: "**Workflow halted.** Coverage Analysis saved to `{outputFile}`. Generate skills and re-run [VS] when ready." HALT (exit code 8, `halt_reason: "analysis-halted"`).
161
172
  - IF C: "**Continuing with 0% coverage — results will be limited.**"
162
173
 
163
174
  Load, read the full file and then execute `{nextStepFile}`.
@@ -0,0 +1,14 @@
1
+ # Exit Codes
2
+
3
+ Every hard halt in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text. Each code pairs with a `halt_reason` string carried in the headless result envelope.
4
+
5
+ | Code | Meaning | Raised by |
6
+ | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
7
+ | 0 | success | step 7 (terminal) |
8
+ | 2 | input-missing / input-invalid | step 1 §1 (headless missing `architecture-doc` arg, or invalid path) → `input-missing`; non-existent file → `input-invalid` |
9
+ | 3 | resolution-failure | step 1 §2 (`{skills_output_folder}` does not exist or is empty → `skills-folder-missing`); step 1 §3 (forge_data_folder unconfigured → `forge-folder-unconfigured`); any stage that cannot resolve a required shared helper (atomic-write, schema ref, validate-feasibility-report) from its probe order → `resolution-failure` |
10
+ | 4 | write-failure | On-Activation §5 pre-flight write probe; step 1 §4 (atomic write of report skeleton failed); step 6 §4b (result-contract write failed) |
11
+ | 5 | state-conflict | step 1 §3 (fewer than 2 valid skills found — stack requires ≥2 → `insufficient-skills`); step 1 §1 (`previousReport` resolves to same inode as `{outputFile}` → `previous-report-collision`); step 6 §1 (report section order or schemaVersion mismatch → `schema-violation`) |
12
+ | 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 6 menu cancelled |
13
+ | 7 | inventory-unreliable | step 1 §2 (>20% subagent failures or enumerate-stack-skills warnings exceed budget); step 3 §3 (>20% API-surface subagents return malformed JSON) |
14
+ | 8 | analysis-halted | coverage.md §7 & integrations.md §7 — user picks [X] at an elective vacuous-analysis gate. These gates are interactive-only; in headless they auto-continue, so exit 8 never fires headlessly and `analysis-halted` never appears in the result envelope. |
@@ -24,4 +24,4 @@ Chain to the shared workflow self-improvement health check at `{nextStepFile}`.
24
24
  Attempt to load `{nextStepFile}`.
25
25
 
26
26
  - **If `{nextStepFile}` loads successfully:** Read it fully, then execute it.
27
- - **If `{nextStepFile}` cannot be resolved or loaded** (e.g., running against a partial installation, module root not resolvable, or the file has been removed): log exactly `health-check unavailable at {path}` (substitute the attempted resolved path) to the user-visible output and exit the workflow cleanly. Do NOT HALT with an error — the health check is an optional self-improvement hook, and the feasibility report (written in step 6) is the authoritative workflow output. Exit cleanly so CI and headless runs do not fail on a missing optional hook.
27
+ - **If `{nextStepFile}` cannot be resolved or loaded** (e.g., running against a partial installation, module root not resolvable, or the file has been removed): log exactly `health-check unavailable at {path}` (substitute the attempted resolved path) to the user-visible output and exit the workflow cleanly. This is not an error halt — the health check is an optional self-improvement hook, and the feasibility report (written in step 6) is the authoritative workflow output. Exiting cleanly keeps CI and headless runs from failing on a missing optional hook.
@@ -6,20 +6,15 @@ feasibilitySchemaProbeOrder:
6
6
  atomicWriteProbeOrder:
7
7
  - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
8
8
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
9
- # {outputFile} and {outputFileLatest} resolve from the activation-stored
10
- # {project_slug}, {timestamp}, and {outputFolderPath} variables (set in
11
- # SKILL.md On Activation §2 + §4). The activation-stored values are
12
- # fixed for the entire run, so every later reference sees the same
13
- # filename — no order-of-operations bug.
14
9
  outputFile: '{outputFolderPath}/feasibility-report-{project_slug}-{timestamp}.md'
15
10
  outputFileLatest: '{outputFolderPath}/feasibility-report-{project_slug}-latest.md'
16
11
  # Resolve `{enumerateStackSkillsHelper}` by probing
17
12
  # `{enumerateStackSkillsProbeOrder}` in order (installed SKF module path
18
13
  # first, src/ dev-checkout fallback); first existing path wins. §2 calls
19
14
  # it for the deterministic skills inventory (cascade-resolved exports,
20
- # metadata-hash for change-detection, confidence-tier mapping). HALT if
21
- # neither candidate exists — falls through to LLM-driven subagent fan-out
22
- # only as graceful degradation, see §2.
15
+ # metadata-hash for change-detection, confidence-tier mapping). If neither
16
+ # candidate exists, §2 does NOT halt it falls through to the LLM-driven
17
+ # subagent fan-out as graceful degradation (see §2).
23
18
  enumerateStackSkillsProbeOrder:
24
19
  - '{project-root}/_bmad/skf/shared/scripts/skf-enumerate-stack-skills.py'
25
20
  - '{project-root}/src/shared/scripts/skf-enumerate-stack-skills.py'
@@ -67,10 +62,16 @@ Wait for user input. **GATE [default: use args]** — If `{headless_mode}` and `
67
62
 
68
63
  **Validate previous report (if provided):**
69
64
  - Confirm the file exists and is readable
70
- - **Collision check:** Resolve `{outputFile}` from the activation-stored `{outputFolderPath}`, `{project_slug}`, and `{timestamp}` (all three computed in SKILL.md On Activation §2 + §4 — they are fixed for the entire run). Then compare both the provided path and `{outputFile}` via `(st_dev, st_ino)` tuples obtained from `stat(2)` on each path (do not rely on absolute-path string equality — symlinks, bind mounts, and case-insensitive filesystems can defeat string comparison; the `(st_dev, st_ino)` comparison is the canonical kernel-level equivalent of `os.path.realpath`-based equality and is strictly stronger because it also catches hardlinks). If `{outputFile}` does not yet exist, resolve its parent via `realpath`, stat that directory, and combine `(st_dev, parent_ino, basename)` for comparison. If the two paths resolve to the same inode, warn: "The previous report path points to the same inode as the new report. This file will be overwritten during this run. Provide a path to a backup copy, or leave empty to skip delta comparison." HALT (exit code 5, `halt_reason: "previous-report-collision"`) until resolved. In headless, emit the error envelope.
65
+ - **Collision check:** Resolve `{outputFile}` from the activation-stored `{outputFolderPath}`, `{project_slug}`, and `{timestamp}`. Then compare both the provided path and `{outputFile}` via `(st_dev, st_ino)` tuples obtained from `stat(2)` on each path (do not rely on absolute-path string equality — symlinks, bind mounts, and case-insensitive filesystems can defeat string comparison). If `{outputFile}` does not yet exist, resolve its parent via `realpath`, stat that directory, and combine `(st_dev, parent_ino, basename)` for comparison. If the two paths resolve to the same inode, warn: "The previous report path points to the same inode as the new report. This file will be overwritten during this run. Provide a path to a backup copy, or leave empty to skip delta comparison." HALT (exit code 5, `halt_reason: "previous-report-collision"`) until resolved. In headless, emit the error envelope.
71
66
  - If missing → "Previous report not found at `{path}`. Proceeding without delta comparison."
72
67
  - Store as `previousReport: {path}` (or empty string if not provided)
73
68
 
69
+ **Auto-discover a prior report (only when none was provided above):** If neither `--previous-report` nor an interactive previous-report path was given, glob `{outputFolderPath}` for `feasibility-report-{project_slug}-*.md`, excluding this run's `{timestamp}` file and `feasibility-report-{project_slug}-latest.md`. Every run writes a new timestamped report, so a usable prior usually already persists on disk with no manual backup. If one or more matches remain, pick the most recent by the embedded `YYYYMMDD-HHmmss` timestamp:
70
+ - Interactive: offer "Found a prior report from `{date}` — compare against it? **[Y]** use it / paste a different path / **[skip]**". On [Y], set `previousReport` to that path; on a pasted path, use it; on `skip`, leave `previousReport` empty.
71
+ - **GATE [default: Y]** — If `{headless_mode}`: auto-select the most recent match, set `previousReport` to it, log: "headless: delta comparison against `{path}`".
72
+ - Excluding this run's `{timestamp}` and `-latest` files guarantees the auto-selected path is a distinct inode from `{outputFile}`, so it can never trip the collision check above.
73
+ - If no match remains, leave `previousReport` empty — first run, no delta.
74
+
74
75
  ### 2. Scan Skills Folder
75
76
 
76
77
  **Pre-flight — skills folder existence:**
@@ -82,18 +83,20 @@ Wait for user input. **GATE [default: use args]** — If `{headless_mode}` and `
82
83
  **Primary path — deterministic enumeration via shared helper:**
83
84
 
84
85
  ```bash
85
- python3 {enumerateStackSkillsHelper} enumerate {skills_output_folder}
86
+ python3 {enumerateStackSkillsHelper} enumerate {skills_output_folder} --reliability
86
87
  ```
87
88
 
88
89
  The helper walks `{skills_output_folder}`, reads each `metadata.json`, applies the exports cascade (metadata → references/ → SKILL.md prose), maps `confidence_tier` (T1/T2/T1-low), captures stack-skill cycles via `composes:`, and emits structured JSON with one entry per skill plus a top-level `warnings[]` array. Cache the result as `skill_inventory` (used by §3, §4, §5, and the integrations + coverage stages).
89
90
 
90
91
  Each helper-emitted entry includes: `skill_name`, `version`, `language`, `confidence_tier`, `exports` (cascade-resolved), `source_repo`, `source_root`, plus a `metadata_hash` for change-detection across runs. The helper's `warnings[]` carries per-skill skip reasons (missing manifest, malformed JSON, non-symlink `active`, orphan-versions, schema-version violations).
91
92
 
92
- **Failure-budget guard:** If `len(warnings) / len(skill_inventory) + len(warnings) > 0.20` (the same 20% threshold chosen so a single malformed skill in a small 3-5 skill inventory does not trip the halt), HALT (exit code 7, `halt_reason: "inventory-unreliable"`) with: "Inventory scan unreliable — {len(warnings)}/{total} skills returned malformed metadata or skip warnings. Re-run [VS] after skills stabilize." In headless, emit the error envelope.
93
+ `--reliability` additionally attaches the inventory reliability verdict: `inventory_reliable` (bool), `unreliable_ratio` (float), `skill_count`, and `warning_count`.
94
+
95
+ **Failure-budget guard:** Read `inventory_reliable` from the helper JSON. If it is `false`, HALT (exit code 7, `halt_reason: "inventory-unreliable"`) with: "Inventory scan unreliable — {warning_count}/{skill_count + warning_count} skills returned malformed metadata or skip warnings. Re-run [VS] after skills stabilize." (substitute `warning_count` and `skill_count + warning_count` from the helper JSON). In headless, emit the error envelope. The helper's threshold is chosen so a single malformed skill in a small 3-5 skill inventory does not trip the halt.
93
96
 
94
97
  **Capture mtime:** For each accepted skill in `skill_inventory`, also record `metadata.json`'s mtime via `stat` into the entry as `metadata_mtime`. Step-03 will re-verify this to detect mid-run modifications.
95
98
 
96
- **Fallback path — graceful degradation when the helper is unavailable:** If `{enumerateStackSkillsHelper}` has no existing candidate (e.g. partial installation), fall through to the LLM-driven subagent fan-out: launch up to **8 subagents concurrently**, each reading one resolved skill package's `metadata.json` and returning the same JSON shape the helper would emit. Apply the same 20% failure-budget guard. This path exists so verify-stack survives a missing helper, but the deterministic helper is preferred it has a tested cascade resolver, captures the metadata-hash, and surfaces every skip reason in `warnings[]` consistently.
99
+ **Fallback path — graceful degradation when the helper is unavailable:** If `{enumerateStackSkillsHelper}` has no existing candidate (e.g. partial installation), fall through to the LLM-driven subagent fan-out: launch up to **8 subagents concurrently**, each reading one resolved skill package's `metadata.json` and returning the same JSON shape the helper would emit. In this branch `inventory_reliable` is unavailable, so compute the failure-budget guard inline: when `warning_count / (skill_count + warning_count) > 0.20`, HALT (exit code 7, `halt_reason: "inventory-unreliable"`) with the same message as the primary path. In headless, emit the error envelope.
97
100
 
98
101
  ### 3. Validate Minimum Requirements
99
102
 
@@ -107,23 +110,15 @@ Each helper-emitted entry includes: `skill_name`, `version`, `language`, `confid
107
110
  - If undefined or empty → "**Cannot proceed.** `forge_data_folder` is not configured in config.yaml. Re-run [SF] Setup Forge to initialize."
108
111
  - HALT (exit code 3, `halt_reason: "forge-folder-unconfigured"`). In headless, emit the error envelope.
109
112
 
110
- **Check architecture document:**
111
- - Confirm it was loaded successfully in section 1
112
- - If not → HALT with error (should not reach here if section 1 validation passed)
113
-
114
113
  ### 4. Create Feasibility Report
115
114
 
116
- **Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. HALT if no candidate exists.
115
+ **Resolve `{atomicWriteHelper}`** from `{atomicWriteProbeOrder}`; first existing path wins. If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
117
116
 
118
- **Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback). HALT if no candidate exists.
117
+ **Resolve `{feasibilitySchemaRef}`** from `{feasibilitySchemaProbeOrder}`; first existing path wins (installed SKF module path first, dev-checkout `src/` fallback). If no candidate exists: HALT (exit code 3, `halt_reason: "resolution-failure"`); in headless, emit the error envelope.
119
118
 
120
- This skill is the PRODUCER of the feasibility report schema defined in `{feasibilitySchemaRef}`. All outputs MUST conform to that schema — in particular: `schemaVersion: "1.0"`, the defined verdict token set (`Verified|Plausible|Risky|Blocked`; overall `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE`), the filename pattern, and the section-heading order.
119
+ This skill produces the feasibility report schema defined in `{feasibilitySchemaRef}`; every output conforms to that schema — `schemaVersion: "1.0"`, the verdict token set (`Verified|Plausible|Risky|Blocked`; overall `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE`), the filename pattern, and the section-heading order.
121
120
 
122
- **Filename variables** (already computed at activation do not re-derive):
123
- - `project_slug`: set in SKILL.md On Activation §2 from `project_name`
124
- - `timestamp`: set in SKILL.md On Activation §2 (UTC `YYYYMMDD-HHmmss`, fixed for the run)
125
- - `outputFile` resolves to `{outputFolderPath}/feasibility-report-{project_slug}-{timestamp}.md` per the stage frontmatter template
126
- - `outputFileLatest` resolves to `{outputFolderPath}/feasibility-report-{project_slug}-latest.md` (a copy, not a symlink — per schema)
121
+ **Filename variables:** `project_slug` and `timestamp` were fixed at activation per SKILL.md On Activation §2; reuse them, do not re-derive. `{outputFile}` and `{outputFileLatest}` resolve from them per the stage frontmatter template — the latter a copy, not a symlink (per schema).
127
122
 
128
123
  **Load** `{reportTemplatePath}` (the customize-aware template path resolved in SKILL.md On Activation §4) and stage the initial content. Substitute the template's `Schema contract:` line `{feasibilitySchemaRef}` placeholder with the resolved schema path so every emitted report cites a path that exists on the running machine.
129
124
 
@@ -145,7 +140,7 @@ This skill is the PRODUCER of the feasibility report schema defined in `{feasibi
145
140
  - `skillsAnalyzed: {count}`
146
141
  - `stepsCompleted: ['init']`
147
142
 
148
- **Atomic write:** Pipe the staged content through `python3 {atomicWriteHelper} write --target {outputFile}` and then again with `--target {outputFileLatest}`. Both writes use the same staged content. Do NOT use `rm`+rewrite; do NOT create a symlink for the `-latest` copy.
143
+ **Atomic write:** Pipe the staged content through `python3 {atomicWriteHelper} write --target {outputFile}` and then again with `--target {outputFileLatest}`. Both writes use the same staged content through the atomic helper — a plain `rm`+rewrite risks a partial write corrupting the report, and the `-latest` file is a copy, not a symlink (per the shared schema).
149
144
 
150
145
  On any non-zero exit from either write: HALT (exit code 4, `halt_reason: "write-failed"`) and emit the error envelope per SKILL.md "Result Contract (Headless)" with `report_path: null`, `report_latest_path: null`, `overall_verdict: null`.
151
146
 
@@ -12,9 +12,9 @@ Token set is defined canonically in the SKF shared feasibility report schema (`_
12
12
 
13
13
  | Verdict | Meaning | Required Evidence |
14
14
  |---------------|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
15
- | **Verified** | APIs demonstrably connect AND docs cross-reference each other | Check 1 (language) passes with declared evidence; Check 3 (types) passes from cited `exports` signatures; **Check 4 (docs cross-reference) MUST pass with a literal substring/name citation** — without Check 4 evidence, cap at `Plausible`. Check 2 is best-effort only and cannot by itself promote to `Verified`. |
15
+ | **Verified** | APIs demonstrably connect and docs cross-reference each other | Check 1 (language) passes with declared evidence; Check 3 (types) passes from cited `exports` signatures; **Check 4 (docs cross-reference) passes with a literal substring/name citation** — without Check 4 evidence, cap at `Plausible`. Check 2 is best-effort only and cannot by itself promote to `Verified`. |
16
16
  | **Plausible** | Checks pass but rely on inferred or indirect evidence | Language + type checks pass; Check 2 uses inferred `protocols_inferred`/`data_formats_inferred` (prose scan); Check 4 is weak or missing (no literal cross-reference). This is the mandatory cap whenever Check 4 does not surface a literal citation. |
17
- | **Risky** | Type mismatch, protocol gap, or language boundary requiring a bridge | A clear gap exists (e.g., TypeScript↔Rust FFI needed) but a workaround is architecturally feasible — a named workaround MUST be cited in the recommendation |
17
+ | **Risky** | Type mismatch, protocol gap, or language boundary requiring a bridge | A clear gap exists (e.g., TypeScript↔Rust FFI needed) but a workaround is architecturally feasible — cite a named workaround in the recommendation |
18
18
  | **Blocked** | Fundamental incompatibility — no feasible integration path even with a bridge or adapter layer | The two libraries cannot exchange data in any documented way; requires replacing one of the libraries |
19
19
 
20
20
  **Promotion rule:** `Verified` requires Check 4 evidence. If Checks 1 and 3 pass but Check 4 fails (no literal substring/name citation from either skill's SKILL.md), the verdict is capped at `Plausible`. This rule is enforced by step 3 §4 and is the producer obligation declared in the shared schema.
@@ -27,22 +27,13 @@ For each integration pair (Library A ↔ Library B):
27
27
 
28
28
  ### 1. Language Boundary Check
29
29
 
30
- | A Language | B Language | Assessment |
31
- |---------------------|----------------------------------------|----------------------------------------------------------------------------------|
32
- | Same language | Same language | No boundary — direct API calls possible |
33
- | TypeScript ↔ Rust | Requires FFI, IPC, or WebSocket bridge | Check if a bridge library exists in the stack (e.g., Tauri provides JS↔Rust IPC) |
34
- | TypeScript ↔ Python | Requires REST/gRPC/WebSocket bridge | Typically not direct |
35
- | Any ↔ C/C++ | FFI available in most languages | Check for bindings |
30
+ - Same language on both sides → no boundary, direct API calls. Different languages → the integration needs a bridge (FFI, IPC, or a network protocol; the mechanism follows from the pair — e.g. C/C++ exposes FFI most languages can bind).
31
+ - The load-bearing nudge: **check whether a bridge library already exists in the stack** before assuming one must be built (e.g., Tauri provides JS↔Rust IPC).
36
32
 
37
33
  ### 2. Protocol Compatibility Check
38
34
 
39
- | A Protocol | B Protocol | Assessment |
40
- |---------------------------|-------------------|-------------------------------------------------------------|
41
- | In-process (same runtime) | In-process | Direct — function calls |
42
- | HTTP/REST | HTTP/REST | Network bridge — compatible if API endpoints match |
43
- | WebSocket | WebSocket | Real-time bridge — check message format compatibility |
44
- | Shared filesystem | Shared filesystem | Async — check format compatibility |
45
- | Embedded database | Embedded database | May conflict on lock files — check for multi-writer support |
35
+ - Matching transports are compatible modulo format alignment: both in-process → direct calls; both HTTP/REST → compatible if endpoints match; both WebSocket → check message-format compatibility; both shared-filesystem → async, check format.
36
+ - The load-bearing nudge: **two embedded databases may conflict on lock files — check for multi-writer support** before treating them as compatible.
46
37
 
47
38
  ### 3. Type Compatibility Check
48
39
 
@@ -54,15 +45,15 @@ For each integration pair (Library A ↔ Library B):
54
45
 
55
46
  - Search Skill A's SKILL.md for a literal substring/name citation of Library B
56
47
  - Search Skill B's SKILL.md for the reciprocal citation
57
- - Accept literal names or aliases declared in that skill's metadata; do NOT accept paraphrase or fuzzy matches
58
- - A pass requires at least one literal citation in at least one direction; the exact substring and location MUST be recorded in the evidence block
59
- - If neither skill literally cites the other, Check 4 FAILS and the per-pair verdict MUST cap at `Plausible` (not `Verified`)
48
+ - Accept literal names or aliases declared in that skill's metadata; a paraphrase or fuzzy match does not satisfy Check 4
49
+ - A pass requires at least one literal citation in at least one direction; record the exact substring and location in the evidence block
50
+ - If neither skill literally cites the other, Check 4 fails and the per-pair verdict caps at `Plausible` (not `Verified`)
60
51
 
61
52
  ---
62
53
 
63
54
  ## Verdict Evidence Format
64
55
 
65
- Each verdict MUST include:
56
+ Each verdict includes:
66
57
 
67
58
  ```
68
59
  **{Library A} ↔ {Library B}: {VERDICT}**