bmad-module-skill-forge 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +51 -49
  3. package/docs/404.md +10 -8
  4. package/docs/_data/pinned.yaml +22 -13
  5. package/docs/_internal/STABILITY.md +58 -35
  6. package/docs/agents.md +39 -53
  7. package/docs/architecture.md +65 -51
  8. package/docs/bmad-synergy.md +53 -51
  9. package/docs/campaign.md +127 -75
  10. package/docs/concepts.md +34 -28
  11. package/docs/examples.md +70 -68
  12. package/docs/forge-auto.md +34 -31
  13. package/docs/getting-started.md +118 -57
  14. package/docs/how-it-works.md +25 -21
  15. package/docs/index.md +15 -15
  16. package/docs/skill-model.md +70 -59
  17. package/docs/troubleshooting.md +135 -18
  18. package/docs/verifying-a-skill.md +83 -57
  19. package/docs/why-skf.md +24 -22
  20. package/docs/workflows.md +274 -110
  21. package/package.json +18 -10
  22. package/src/forger/preferences.yaml +8 -1
  23. package/src/knowledge/agentskills-spec.md +2 -0
  24. package/src/knowledge/ccc-bridge.md +40 -24
  25. package/src/knowledge/confidence-tiers.md +9 -5
  26. package/src/knowledge/overview.md +3 -3
  27. package/src/knowledge/progressive-capability.md +8 -8
  28. package/src/knowledge/provenance-tracking.md +9 -5
  29. package/src/knowledge/qmd-registry.md +2 -2
  30. package/src/knowledge/skf-knowledge-index.csv +2 -2
  31. package/src/knowledge/skill-lifecycle.md +18 -11
  32. package/src/knowledge/split-body-strategy.md +0 -3
  33. package/src/knowledge/tool-resolution.md +4 -2
  34. package/src/knowledge/version-paths.md +101 -15
  35. package/src/module-help.csv +3 -3
  36. package/src/module.yaml +10 -0
  37. package/src/shared/data/ast-grep-recipes.yaml +608 -0
  38. package/src/shared/data/ide-context-files.json +30 -0
  39. package/src/shared/health-check.md +32 -12
  40. package/src/shared/references/description-guard-protocol.md +14 -9
  41. package/src/shared/references/feasibility-report-schema.md +19 -13
  42. package/src/shared/references/headless-gate-convention.md +55 -3
  43. package/src/shared/references/output-contract-schema.md +19 -8
  44. package/src/shared/references/pipeline-contracts.md +94 -29
  45. package/src/shared/references/tessl-review.md +77 -0
  46. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
  47. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
  48. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
  49. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
  50. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
  51. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
  52. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
  53. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
  54. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
  55. package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
  56. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
  57. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
  58. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
  59. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
  60. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
  61. package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
  62. package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
  63. package/src/shared/scripts/skf-atomic-write.py +30 -14
  64. package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
  65. package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
  66. package/src/shared/scripts/skf-check-unit-records.py +184 -0
  67. package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
  68. package/src/shared/scripts/skf-classify-changed-files.py +694 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
  71. package/src/shared/scripts/skf-count-imports.py +1867 -0
  72. package/src/shared/scripts/skf-count-tokens.py +1 -1
  73. package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
  74. package/src/shared/scripts/skf-description-guard.py +117 -9
  75. package/src/shared/scripts/skf-detect-docs.py +892 -4
  76. package/src/shared/scripts/skf-detect-language.py +486 -107
  77. package/src/shared/scripts/skf-detect-registry.py +887 -0
  78. package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
  79. package/src/shared/scripts/skf-detect-tools.py +215 -27
  80. package/src/shared/scripts/skf-detect-workspaces.py +506 -30
  81. package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
  82. package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
  83. package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
  84. package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
  85. package/src/shared/scripts/skf-extract-public-api.py +4084 -85
  86. package/src/shared/scripts/skf-extraction-inventory.py +746 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
  88. package/src/shared/scripts/skf-fetch-temporal.py +674 -0
  89. package/src/shared/scripts/skf-find-cycles.py +97 -29
  90. package/src/shared/scripts/skf-find-test-report.py +476 -0
  91. package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +661 -0
  94. package/src/shared/scripts/skf-hash-content.py +166 -12
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +159 -27
  97. package/src/shared/scripts/skf-manifest-ops.py +1 -1
  98. package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
  99. package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
  100. package/src/shared/scripts/skf-names-present.py +221 -0
  101. package/src/shared/scripts/skf-pair-intersect.py +119 -28
  102. package/src/shared/scripts/skf-parse-gaps.py +1384 -0
  103. package/src/shared/scripts/skf-preapply.py +1 -1
  104. package/src/shared/scripts/skf-preflight.py +161 -27
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
  107. package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
  108. package/src/shared/scripts/skf-quick-batch.py +774 -0
  109. package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
  110. package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
  111. package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
  112. package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
  113. package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
  114. package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
  115. package/src/shared/scripts/skf-resolve-package.py +523 -106
  116. package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
  117. package/src/shared/scripts/skf-run-lock.py +577 -0
  118. package/src/shared/scripts/skf-scan-manifests.py +998 -57
  119. package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
  120. package/src/shared/scripts/skf-severity-classify.py +712 -108
  121. package/src/shared/scripts/skf-shape-detect.py +298 -204
  122. package/src/shared/scripts/skf-shard-body.py +9 -8
  123. package/src/shared/scripts/skf-skill-inventory.py +2412 -89
  124. package/src/shared/scripts/skf-skills-module.py +472 -0
  125. package/src/shared/scripts/skf-source-tree.py +1796 -0
  126. package/src/shared/scripts/skf-structural-diff.py +670 -119
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +5 -5
  129. package/src/shared/scripts/skf-update-run-state.py +624 -0
  130. package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
  131. package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
  132. package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
  133. package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
  134. package/src/shared/scripts/skf-validate-output.py +712 -64
  135. package/src/shared/scripts/skf-validate-pins.py +1 -1
  136. package/src/shared/scripts/skf-verify-no-trace.py +116 -10
  137. package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
  138. package/src/shared/scripts/skf-write-skill-brief.py +367 -19
  139. package/src/shared/tool-requirements.yaml +201 -0
  140. package/src/skf-analyze-source/SKILL.md +41 -36
  141. package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
  142. package/src/skf-analyze-source/customize.toml +36 -24
  143. package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
  144. package/src/skf-analyze-source/references/continue.md +2 -13
  145. package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
  146. package/src/skf-analyze-source/references/generate-briefs.md +121 -55
  147. package/src/skf-analyze-source/references/headless-contract.md +42 -15
  148. package/src/skf-analyze-source/references/identify-units.md +96 -110
  149. package/src/skf-analyze-source/references/init.md +73 -71
  150. package/src/skf-analyze-source/references/map-and-detect.md +123 -105
  151. package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
  152. package/src/skf-analyze-source/references/recommend.md +51 -70
  153. package/src/skf-analyze-source/references/scan-project.md +60 -84
  154. package/src/skf-analyze-source/references/scan-root.md +39 -0
  155. package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
  156. package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
  157. package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
  158. package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
  159. package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
  160. package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
  161. package/src/skf-audit-skill/SKILL.md +43 -51
  162. package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
  163. package/src/skf-audit-skill/customize.toml +41 -20
  164. package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
  165. package/src/skf-audit-skill/references/doc-drift.md +141 -0
  166. package/src/skf-audit-skill/references/headless-contract.md +29 -0
  167. package/src/skf-audit-skill/references/init.md +148 -210
  168. package/src/skf-audit-skill/references/re-index.md +96 -108
  169. package/src/skf-audit-skill/references/report.md +96 -33
  170. package/src/skf-audit-skill/references/semantic-diff.md +27 -18
  171. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  172. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  173. package/src/skf-audit-skill/references/structural-diff.md +75 -90
  174. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  175. package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
  176. package/src/skf-brief-skill/SKILL.md +36 -25
  177. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  178. package/src/skf-brief-skill/assets/scope-templates.md +17 -24
  179. package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
  180. package/src/skf-brief-skill/customize.toml +48 -20
  181. package/src/skf-brief-skill/references/analyze-target.md +142 -125
  182. package/src/skf-brief-skill/references/confirm-brief.md +10 -8
  183. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  184. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  185. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  186. package/src/skf-brief-skill/references/headless-args.md +84 -16
  187. package/src/skf-brief-skill/references/health-check.md +1 -1
  188. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  189. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  190. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  191. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  192. package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
  193. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  194. package/src/skf-brief-skill/references/version-resolution.md +3 -2
  195. package/src/skf-brief-skill/references/write-brief.md +66 -57
  196. package/src/skf-campaign/SKILL.md +31 -34
  197. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  198. package/src/skf-campaign/customize.toml +47 -16
  199. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  200. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  201. package/src/skf-campaign/references/health-check.md +7 -16
  202. package/src/skf-campaign/references/step-01-setup.md +46 -84
  203. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  204. package/src/skf-campaign/references/step-03-pins.md +26 -18
  205. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  206. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  207. package/src/skf-campaign/references/step-06-batch.md +41 -34
  208. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  209. package/src/skf-campaign/references/step-08-verify.md +28 -29
  210. package/src/skf-campaign/references/step-09-refine.md +29 -30
  211. package/src/skf-campaign/references/step-10-export.md +48 -36
  212. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  213. package/src/skf-campaign/references/step-resume.md +37 -68
  214. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  215. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  216. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  217. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  218. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  219. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  220. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  221. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  222. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  223. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  224. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  225. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  226. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  227. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  228. package/src/skf-create-skill/SKILL.md +27 -20
  229. package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
  230. package/src/skf-create-skill/assets/skill-sections.md +17 -6
  231. package/src/skf-create-skill/customize.toml +20 -8
  232. package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
  233. package/src/skf-create-skill/references/batch-mode.md +81 -0
  234. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  235. package/src/skf-create-skill/references/compile.md +107 -29
  236. package/src/skf-create-skill/references/component-extraction.md +113 -91
  237. package/src/skf-create-skill/references/enrich.md +41 -23
  238. package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
  239. package/src/skf-create-skill/references/extract.md +200 -161
  240. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  241. package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
  242. package/src/skf-create-skill/references/extraction-patterns.md +584 -196
  243. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  244. package/src/skf-create-skill/references/health-check.md +1 -1
  245. package/src/skf-create-skill/references/load-brief.md +53 -36
  246. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  247. package/src/skf-create-skill/references/report.md +28 -59
  248. package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
  249. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  250. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  251. package/src/skf-create-skill/references/step-doc-sources.md +55 -18
  252. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  253. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
  254. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  255. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  256. package/src/skf-create-skill/references/validate.md +173 -99
  257. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  258. package/src/skf-create-stack-skill/SKILL.md +29 -56
  259. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  260. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  261. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  262. package/src/skf-create-stack-skill/customize.toml +23 -12
  263. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  264. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  265. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  266. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  267. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  268. package/src/skf-create-stack-skill/references/init.md +90 -32
  269. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  270. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  271. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  272. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  273. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  274. package/src/skf-create-stack-skill/references/report.md +39 -33
  275. package/src/skf-create-stack-skill/references/validate.md +20 -39
  276. package/src/skf-drop-skill/SKILL.md +28 -51
  277. package/src/skf-drop-skill/customize.toml +44 -30
  278. package/src/skf-drop-skill/references/execute.md +79 -146
  279. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  280. package/src/skf-drop-skill/references/report.md +46 -30
  281. package/src/skf-drop-skill/references/select.md +118 -124
  282. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  283. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  284. package/src/skf-export-skill/SKILL.md +40 -62
  285. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  286. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  287. package/src/skf-export-skill/customize.toml +42 -13
  288. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  289. package/src/skf-export-skill/references/health-check.md +2 -0
  290. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  291. package/src/skf-export-skill/references/load-skill.md +88 -62
  292. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  293. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  294. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  295. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  296. package/src/skf-export-skill/references/package.md +15 -73
  297. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  298. package/src/skf-export-skill/references/result-envelope.md +31 -16
  299. package/src/skf-export-skill/references/summary.md +41 -17
  300. package/src/skf-export-skill/references/token-report.md +4 -12
  301. package/src/skf-export-skill/references/update-context.md +118 -237
  302. package/src/skf-forger/SKILL.md +61 -37
  303. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  304. package/src/skf-forger/customize.toml +43 -0
  305. package/src/skf-forger/references/pipeline-mode.md +73 -18
  306. package/src/skf-forger/scripts/forge-status.py +299 -0
  307. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  308. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  309. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  310. package/src/skf-quick-skill/SKILL.md +54 -53
  311. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  312. package/src/skf-quick-skill/customize.toml +51 -20
  313. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  314. package/src/skf-quick-skill/references/compile.md +30 -31
  315. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  316. package/src/skf-quick-skill/references/finalize.md +69 -18
  317. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  318. package/src/skf-quick-skill/references/health-check.md +5 -3
  319. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  320. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  321. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  322. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  323. package/src/skf-refine-architecture/SKILL.md +43 -53
  324. package/src/skf-refine-architecture/customize.toml +49 -20
  325. package/src/skf-refine-architecture/references/compile.md +97 -54
  326. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  327. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  328. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  329. package/src/skf-refine-architecture/references/improvements.md +14 -20
  330. package/src/skf-refine-architecture/references/init.md +98 -57
  331. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  332. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  333. package/src/skf-refine-architecture/references/report.md +65 -54
  334. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  335. package/src/skf-rename-skill/SKILL.md +19 -54
  336. package/src/skf-rename-skill/customize.toml +32 -26
  337. package/src/skf-rename-skill/references/execute.md +230 -184
  338. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  339. package/src/skf-rename-skill/references/health-check.md +1 -7
  340. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  341. package/src/skf-rename-skill/references/report.md +63 -52
  342. package/src/skf-rename-skill/references/select.md +197 -115
  343. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  344. package/src/skf-setup/SKILL.md +26 -26
  345. package/src/skf-setup/customize.toml +14 -10
  346. package/src/skf-setup/references/auto-index.md +50 -63
  347. package/src/skf-setup/references/ccc-index.md +29 -89
  348. package/src/skf-setup/references/detect-and-tier.md +58 -66
  349. package/src/skf-setup/references/health-check.md +2 -0
  350. package/src/skf-setup/references/invocation-contract.md +14 -0
  351. package/src/skf-setup/references/report.md +43 -166
  352. package/src/skf-setup/references/tier-rules.md +5 -5
  353. package/src/skf-setup/references/write-config.md +34 -57
  354. package/src/skf-test-skill/SKILL.md +17 -57
  355. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  356. package/src/skf-test-skill/customize.toml +27 -13
  357. package/src/skf-test-skill/references/coherence-check.md +134 -62
  358. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  359. package/src/skf-test-skill/references/coverage-check.md +217 -254
  360. package/src/skf-test-skill/references/detect-mode.md +5 -5
  361. package/src/skf-test-skill/references/external-validators.md +94 -82
  362. package/src/skf-test-skill/references/health-check.md +6 -7
  363. package/src/skf-test-skill/references/init.md +163 -88
  364. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  365. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  366. package/src/skf-test-skill/references/report.md +162 -142
  367. package/src/skf-test-skill/references/score.md +80 -107
  368. package/src/skf-test-skill/references/scoring-rules.md +41 -155
  369. package/src/skf-test-skill/references/source-access-protocol.md +19 -92
  370. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  371. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  372. package/src/skf-test-skill/scripts/build-result-context.py +287 -0
  373. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  374. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  375. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  376. package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
  377. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  378. package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
  379. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  380. package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
  381. package/src/skf-test-skill/scripts/score-signatures.py +459 -0
  382. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  383. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  384. package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
  385. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  386. package/src/skf-update-skill/SKILL.md +38 -25
  387. package/src/skf-update-skill/customize.toml +27 -12
  388. package/src/skf-update-skill/references/detect-changes.md +233 -222
  389. package/src/skf-update-skill/references/gap-driven.md +336 -0
  390. package/src/skf-update-skill/references/health-check.md +31 -5
  391. package/src/skf-update-skill/references/init.md +250 -84
  392. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  393. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  394. package/src/skf-update-skill/references/merge.md +180 -47
  395. package/src/skf-update-skill/references/re-extract.md +53 -262
  396. package/src/skf-update-skill/references/report.md +100 -35
  397. package/src/skf-update-skill/references/write.md +331 -149
  398. package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
  399. package/src/skf-verify-stack/SKILL.md +23 -42
  400. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  401. package/src/skf-verify-stack/customize.toml +55 -22
  402. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  403. package/src/skf-verify-stack/references/coverage.md +67 -64
  404. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  405. package/src/skf-verify-stack/references/init.md +107 -62
  406. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  407. package/src/skf-verify-stack/references/integrations.md +106 -65
  408. package/src/skf-verify-stack/references/report.md +59 -42
  409. package/src/skf-verify-stack/references/requirements.md +32 -9
  410. package/src/skf-verify-stack/references/synthesize.md +53 -39
  411. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  412. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  413. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  414. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  415. package/tools/cli/commands/install.js +11 -4
  416. package/tools/cli/commands/status.js +15 -3
  417. package/tools/cli/commands/update.js +11 -4
  418. package/tools/cli/lib/tool-check.js +320 -0
  419. package/tools/cli/lib/version-check.js +22 -9
  420. package/tools/cli/skf-cli.js +4 -1
  421. package/.gitattributes +0 -16
  422. package/.nvmrc +0 -1
  423. package/docs/_internal/RELEASING.md +0 -645
  424. package/release-audits/v1.0.0-launch-audit.md +0 -728
  425. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  426. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  427. package/src/skf-campaign/manifest.yaml +0 -15
  428. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  429. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  430. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  431. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  432. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  433. package/src/skf-update-skill/references/validate.md +0 -124
  434. package/tools/validate-docs-drift.js +0 -233
  435. package/tools/validate-docs-links.js +0 -665
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.11"
4
4
  # dependencies = []
5
5
  # ///
6
6
  """Deterministic overall-feasibility verdict rollup for skf-verify-stack (synthesize.md §1).
@@ -16,7 +16,8 @@ which co-occurring problems to name, how to phrase the recommendation — stays
16
16
  prompt; this script emits only the token plus the stable condition codes the prompt
17
17
  cites when it writes that rationale.
18
18
 
19
- The ladder (exactly mirrors synthesize.md §1; evaluate top-to-bottom, first match wins):
19
+ The ladder (synthesize.md §1 runs it and cites its condition codes; evaluate top-to-bottom,
20
+ first match wins):
20
21
 
21
22
  1. Zero-coverage short-circuit — coveragePercentage == 0 -> NOT_FEASIBLE
22
23
  (no live coverage: analysis is vacuous; the remainder of the ladder is skipped).
@@ -29,23 +30,48 @@ The ladder (exactly mirrors synthesize.md §1; evaluate top-to-bottom, first mat
29
30
  CONDITIONALLY_FEASIBLE.
30
31
 
31
32
  Post-verdict zero-integration-pairs guard (applied after ANY verdict): when all four
32
- integration counts are 0 AND the user continued past a step-2 zero-state [C] gate,
33
- the guard fires — a FEASIBLE verdict is overridden to CONDITIONALLY_FEASIBLE, and
34
- regardless of verdict the prompt appends the "no integration claims found" note.
33
+ integration counts are 0 AND two or more live technologies are Covered
34
+ (coveredCount >= 2), no integration was verified although one could have been, so
35
+ the guard fires: a FEASIBLE verdict is overridden to CONDITIONALLY_FEASIBLE, and
36
+ regardless of verdict the prompt appends the "no integration pair found" note. With
37
+ one Covered technology there is no pair to find, so the guard stays off.
35
38
 
36
39
  Note that coveragePercentage and missingCount are independent inputs on purpose: half-up
37
40
  rounding means a stack with covered=199, missing=1 rounds to coveragePercentage == 100
38
41
  while missingCount is still > 0, so the Missing trigger reads missingCount directly.
42
+ coveredCount is checked against coveragePercentage only where they cannot disagree: no
43
+ Covered technology means 0% coverage, so coveredCount 0 with a percentage above 0 is
44
+ rejected (a caller that left the count at 0 would silently switch the guard off).
39
45
 
40
46
  CLI usage:
47
+ uv run skf-verdict-rollup.py --report <feasibility-report.md> # the report's frontmatter
41
48
  uv run skf-verdict-rollup.py '<JSON>' # JSON literal positional
42
49
  uv run skf-verdict-rollup.py --json-input '<JSON>' # explicit flag form
43
50
  cat input.json | uv run skf-verdict-rollup.py --stdin # piped input
44
51
 
52
+ Reading the report (--report, the call synthesize.md section 1 makes): the
53
+ input below comes from the report's frontmatter, read with split_frontmatter()
54
+ of the shared skf-validate-feasibility-report.py, the reader the report's
55
+ consumers use. coveragePercentage and the four pair counts keep their names;
56
+ coverageCovered gives coveredCount, coverageMissing missingCount and
57
+ coverageReplaced replacedCount; requirementsPass gives requirementsEvaluated
58
+ (true for `completed`, false for `skipped`), and with `completed`,
59
+ requirementsNotAddressed and requirementsPartial come in too. Every one of
60
+ those keys must hold a whole number (requirementsPass one of its two
61
+ values): a key missing from the frontmatter, or still at the template's null
62
+ or empty value, fails the run (INVALID_REPORT) rather than reading as 0 or
63
+ false, so the defaults below serve only the callers that pass JSON. The
64
+ template starts coveragePercentage and the four pair counts at 0, a value
65
+ no stage need have written, so the frontmatter's stepsCompleted list
66
+ (read with the shared reader's frontmatter_list()) must name coverage,
67
+ integrations and requirements, the three stages that write the counts: a
68
+ report whose list lacks one fails the same way.
69
+
45
70
  Input schema (one object; counts come straight from the report frontmatter/tables):
46
71
  {
47
72
  "coveragePercentage": <int 0..100>, # from coverage.md (coverage-tally)
48
73
  "missingCount": <int>, # Missing technologies (Replaced excluded)
74
+ "coveredCount": <int>, # Covered technologies (the coverage tally's covered_count)
49
75
  "pairsBlocked": <int>, # from integrations.md
50
76
  "pairsRisky": <int>,
51
77
  "pairsPlausible": <int>, # includes Check-4-missing caps
@@ -53,45 +79,111 @@ Input schema (one object; counts come straight from the report frontmatter/table
53
79
  "requirementsEvaluated": <bool>, # optional (default false): requirementsPass == "completed"
54
80
  "requirementsNotAddressed": <int>, # optional (default 0); ignored unless evaluated
55
81
  "requirementsPartial": <int>, # optional (default 0); ignored unless evaluated
56
- "continuedPastZeroState": <bool> # optional (default false): user pressed [C] past a step-2 zero-state gate
82
+ "replacedCount": <int> # optional (default 0): Replaced technologies (the coverage tally's replaced_count)
57
83
  }
58
84
 
59
85
  Output (stdout, one object):
60
86
  {
61
87
  "overallVerdict": "FEASIBLE" | "CONDITIONALLY_FEASIBLE" | "NOT_FEASIBLE",
62
88
  "matchedConditions": [<condition codes, in ladder order>],
63
- "zeroPairsGuardFired": <bool>
89
+ "zeroPairsGuardFired": <bool>,
90
+ "recommendationCount": <int>, # the sum of `recommendations`
91
+ "recommendations": {"blocked": <int>, "missing": <int>, "replaced": <int>,
92
+ "risky": <int>, "plausible": <int>, "zeroPairs": 0 | 1,
93
+ "notAddressed": <int>, "partial": <int>}
64
94
  }
65
95
 
96
+ Recommendation count: synthesize.md section 2 writes one recommendation per
97
+ finding that is not verified, so their number has one correct answer per input
98
+ too: one per Blocked, Missing, Replaced, Risky (each circular-dependency row
99
+ included, as the Risky count holds it) and Plausible item, one when the
100
+ zero-integration-pairs guard fired, and, only when the requirements pass ran, one
101
+ per Not Addressed or Partially Fulfilled requirement. `recommendations` gives each
102
+ kind's share in the order synthesize.md lists them, and `recommendationCount` is
103
+ their sum: the report's recommendationCount and the envelope's
104
+ recommendation_count.
105
+
66
106
  Condition codes (stable; the prompt cites these when synthesizing the rationale):
67
107
  zero-coverage · blocked-integration · missing-coverage · risky-integration ·
68
108
  requirements-not-addressed · requirements-partial · plausible-cap · zero-integration-pairs
69
109
 
110
+ Errors (stdout, one object): {"error": "<why>", "code": "<code>"}:
111
+ INVALID_INPUT the counts do not fit the input schema above
112
+ INVALID_REPORT --report: the report cannot be read, a key above is
113
+ missing or holds no count, or stepsCompleted does not
114
+ list a stage that writes them
115
+ HELPER_MISSING --report: the shared skf-validate-feasibility-report.py is
116
+ not in the shared scripts folder beside this skill's folder
117
+
70
118
  Exit codes:
71
- 0 — verdict emitted successfully
72
- 1 — no input / input could not be parsed as JSON
73
- 2 — input parsed but schema/semantics invalid (error object emitted as JSON)
119
+ 0 verdict emitted successfully
120
+ 1 no input, input that could not be parsed as JSON, or HELPER_MISSING
121
+ 2 input parsed but schema/semantics invalid: INVALID_INPUT or
122
+ INVALID_REPORT (error object emitted as JSON)
74
123
  """
75
124
 
76
125
  from __future__ import annotations
77
126
 
78
127
  import argparse
128
+ import importlib.util
79
129
  import json
130
+ import re
80
131
  import sys
132
+ from pathlib import Path
81
133
 
82
134
  VERDICTS = ("FEASIBLE", "CONDITIONALLY_FEASIBLE", "NOT_FEASIBLE")
83
135
  REQUIRED_COUNTS = (
84
136
  "missingCount",
137
+ "coveredCount",
85
138
  "pairsBlocked",
86
139
  "pairsRisky",
87
140
  "pairsPlausible",
88
141
  "pairsVerified",
89
142
  )
90
- OPTIONAL_COUNTS = ("requirementsNotAddressed", "requirementsPartial")
143
+ OPTIONAL_COUNTS = ("requirementsNotAddressed", "requirementsPartial", "replacedCount")
144
+
145
+ # --report: each frontmatter key the stages persist, with the input it fills.
146
+ REPORT_COUNTS = (
147
+ ("coveragePercentage", "coveragePercentage"),
148
+ ("coverageMissing", "missingCount"),
149
+ ("coverageCovered", "coveredCount"),
150
+ ("coverageReplaced", "replacedCount"),
151
+ ("pairsBlocked", "pairsBlocked"),
152
+ ("pairsRisky", "pairsRisky"),
153
+ ("pairsPlausible", "pairsPlausible"),
154
+ ("pairsVerified", "pairsVerified"),
155
+ )
156
+ # --report: the stages that write those counts, each of which the frontmatter's
157
+ # stepsCompleted must list: the template's 0 is no count until its stage ran.
158
+ STEPS_COMPLETED_KEY = "stepsCompleted"
159
+ REPORT_STAGES = ("coverage", "integrations", "requirements")
160
+ REQUIREMENTS_PASS_KEY = "requirementsPass"
161
+ REQUIREMENTS_PASSES = ("completed", "skipped")
162
+ # Read only when requirementsPass is `completed`.
163
+ REQUIREMENT_COUNTS = ("requirementsNotAddressed", "requirementsPartial")
164
+ INVALID_REPORT = "INVALID_REPORT"
165
+ HELPER_MISSING = "HELPER_MISSING"
166
+ # A frontmatter count: a whole number, a trailing comment allowed.
167
+ _COUNT_RE = re.compile(r"([0-9]+)(?:\s+#.*)?")
168
+
169
+ # The shared feasibility-report reader. Installed (under _bmad/skf/ or an
170
+ # IDE's skills folder) and in a dev checkout (src/), shared/ sits beside
171
+ # this skill's folder.
172
+ SHARED_READER = (
173
+ Path(__file__).resolve().parent.parent.parent
174
+ / "shared"
175
+ / "scripts"
176
+ / "skf-validate-feasibility-report.py"
177
+ )
91
178
 
179
+ # The zero-pairs guard needs a pair that could have been found: two Covered
180
+ # technologies (the schema's "two or more live technologies", all of them Covered
181
+ # whenever the verdict could otherwise be FEASIBLE).
182
+ ZERO_PAIRS_MIN_COVERED = 2
92
183
 
93
- def make_error(message):
94
- return {"error": message, "code": "INVALID_INPUT"}
184
+
185
+ def make_error(message, code="INVALID_INPUT"):
186
+ return {"error": message, "code": code}
95
187
 
96
188
 
97
189
  def _is_nonneg_int(value):
@@ -117,9 +209,11 @@ def _validate(inp):
117
209
  if field in inp and inp[field] is not None and not _is_nonneg_int(inp[field]):
118
210
  return f"{field} must be a non-negative integer when present"
119
211
 
120
- for field in ("requirementsEvaluated", "continuedPastZeroState"):
121
- if field in inp and not isinstance(inp[field], bool):
122
- return f"{field} must be a boolean when present"
212
+ if "requirementsEvaluated" in inp and not isinstance(inp["requirementsEvaluated"], bool):
213
+ return "requirementsEvaluated must be a boolean when present"
214
+
215
+ if inp["coveredCount"] == 0 and pct > 0:
216
+ return "coveredCount is 0 but coveragePercentage is above 0: pass the coverage tally's covered_count"
123
217
 
124
218
  return None
125
219
 
@@ -139,7 +233,8 @@ def rollup(inp):
139
233
  req_eval = bool(inp.get("requirementsEvaluated", False))
140
234
  not_addressed = inp.get("requirementsNotAddressed") or 0
141
235
  partial = inp.get("requirementsPartial") or 0
142
- continued = bool(inp.get("continuedPastZeroState", False))
236
+ replaced = inp.get("replacedCount") or 0
237
+ covered = inp["coveredCount"]
143
238
 
144
239
  matched: list[str] = []
145
240
 
@@ -179,43 +274,135 @@ def rollup(inp):
179
274
 
180
275
  # Post-verdict zero-integration-pairs guard.
181
276
  zero_pairs = blocked == 0 and risky == 0 and plausible == 0 and verified == 0
182
- guard_fired = zero_pairs and continued
277
+ guard_fired = zero_pairs and covered >= ZERO_PAIRS_MIN_COVERED
183
278
  if guard_fired:
184
279
  if verdict == "FEASIBLE":
185
280
  verdict = "CONDITIONALLY_FEASIBLE"
186
281
  matched.append("zero-integration-pairs")
187
282
 
283
+ # One recommendation per finding that is not verified (synthesize.md section 2),
284
+ # in the order its recommendation list gives them.
285
+ recommendations = {
286
+ "blocked": blocked,
287
+ "missing": missing,
288
+ "replaced": replaced,
289
+ "risky": risky,
290
+ "plausible": plausible,
291
+ "zeroPairs": 1 if guard_fired else 0,
292
+ "notAddressed": not_addressed if req_eval else 0,
293
+ "partial": partial if req_eval else 0,
294
+ }
188
295
  return {
189
296
  "overallVerdict": verdict,
190
297
  "matchedConditions": matched,
191
298
  "zeroPairsGuardFired": guard_fired,
299
+ "recommendationCount": sum(recommendations.values()),
300
+ "recommendations": recommendations,
192
301
  }
193
302
 
194
303
 
304
+ # --- Reading the report -------------------------------------------------------
305
+
306
+
307
+ def load_reader():
308
+ """Import the shared feasibility-report reader, or None when it is missing."""
309
+ try:
310
+ if not SHARED_READER.is_file():
311
+ return None
312
+ except OSError:
313
+ return None # a folder on the way that cannot be searched
314
+ spec = importlib.util.spec_from_file_location("skf_validate_feasibility_report", SHARED_READER)
315
+ if spec is None or spec.loader is None:
316
+ return None
317
+ module = importlib.util.module_from_spec(spec)
318
+ spec.loader.exec_module(module)
319
+ return module
320
+
321
+
322
+ def _report_count(frontmatter, key):
323
+ """A frontmatter key's whole number, or (None, why it holds none)."""
324
+ if key not in frontmatter:
325
+ return None, f"the report frontmatter has no {key}: the stage that writes it did not record it"
326
+ match = _COUNT_RE.fullmatch(frontmatter[key].strip())
327
+ if match is None:
328
+ return None, (f"the report frontmatter's {key} is {frontmatter[key]!r}, not a whole number: "
329
+ "the stage that writes it did not record it")
330
+ return int(match.group(1)), None
331
+
332
+
333
+ def counts_from_report(frontmatter, steps):
334
+ """The rollup input from a report's frontmatter scalars (split_frontmatter())
335
+ and its stepsCompleted items (frontmatter_list(), None when it has none).
336
+
337
+ Returns (input, None), or (None, error) when a stage that writes the counts
338
+ is not in `steps`, or a key the stages persist is missing or holds no count.
339
+ Unlike rollup(), nothing defaults: requirementsPass must be `completed` or
340
+ `skipped`, and `completed` needs both requirement counts.
341
+ """
342
+ for stage in REPORT_STAGES:
343
+ if stage not in (steps or ()):
344
+ return None, (f"the report frontmatter's {STEPS_COMPLETED_KEY} does not list {stage}: "
345
+ "that stage did not finish, so its counts are the template's")
346
+ inp = {}
347
+ for key, field in REPORT_COUNTS:
348
+ inp[field], err = _report_count(frontmatter, key)
349
+ if err:
350
+ return None, err
351
+ passed = frontmatter.get(REQUIREMENTS_PASS_KEY, "").split("#", 1)[0].strip()
352
+ if passed not in REQUIREMENTS_PASSES:
353
+ return None, (f"the report frontmatter's {REQUIREMENTS_PASS_KEY} is {passed!r}, not completed or "
354
+ "skipped: the requirements stage did not record its pass")
355
+ inp["requirementsEvaluated"] = passed == "completed"
356
+ if inp["requirementsEvaluated"]:
357
+ for key in REQUIREMENT_COUNTS:
358
+ inp[key], err = _report_count(frontmatter, key)
359
+ if err:
360
+ return None, err
361
+ return inp, None
362
+
363
+
364
+ def rollup_report(path, reader):
365
+ """(result, exit code) of the rollup over the report at `path`, read with `reader`."""
366
+ try:
367
+ # utf-8-sig drops a byte order mark, as the shared reader does.
368
+ content = Path(path).read_text(encoding="utf-8-sig")
369
+ except (OSError, UnicodeDecodeError) as exc:
370
+ return make_error(f"cannot read the report {path}: {exc}", INVALID_REPORT), 2
371
+ frontmatter, _body = reader.split_frontmatter(content)
372
+ inp, err = counts_from_report(frontmatter, reader.frontmatter_list(content, STEPS_COMPLETED_KEY))
373
+ if err:
374
+ return make_error(err, INVALID_REPORT), 2
375
+ result = rollup(inp)
376
+ return result, 2 if result.get("code") == "INVALID_INPUT" else 0
377
+
378
+
195
379
  # --- CLI --------------------------------------------------------------------
196
380
 
197
381
 
198
382
  def _build_parser():
199
383
  parser = argparse.ArgumentParser(
200
384
  prog="skf-verdict-rollup",
201
- description=(
202
- "Deterministic overall-feasibility verdict rollup (synthesize.md §1). "
203
- "Consumes the persisted coverage / integration / requirements counts and "
204
- "emits the FEASIBLE / CONDITIONALLY_FEASIBLE / NOT_FEASIBLE token plus the "
205
- "condition codes the prompt cites in its rationale."
206
- ),
385
+ # The module docstring is the contract synthesize.md points --help at:
386
+ # the ladder, the input and output schemas and the condition codes.
387
+ description=__doc__,
207
388
  formatter_class=argparse.RawDescriptionHelpFormatter,
208
389
  epilog=(
209
- "Example:\n"
390
+ "Examples:\n"
391
+ " uv run skf-verdict-rollup.py --report feasibility-report-my-app-20261002-101500.md\n"
210
392
  " uv run skf-verdict-rollup.py "
211
- "'{\"coveragePercentage\":100,\"missingCount\":0,\"pairsBlocked\":0,"
212
- "\"pairsRisky\":0,\"pairsPlausible\":0,\"pairsVerified\":3}'"
393
+ "'{\"coveragePercentage\":100,\"missingCount\":0,\"coveredCount\":4,"
394
+ "\"pairsBlocked\":0,\"pairsRisky\":0,\"pairsPlausible\":0,\"pairsVerified\":3}'"
213
395
  ),
214
396
  )
215
397
  src = parser.add_mutually_exclusive_group()
216
398
  src.add_argument("json_input", nargs="?", help="JSON object as a positional argument.")
217
399
  src.add_argument("--json-input", dest="json_input_flag", help="JSON object passed via flag.")
218
400
  src.add_argument("--stdin", action="store_true", help="Read the JSON object from stdin.")
401
+ src.add_argument(
402
+ "--report",
403
+ metavar="REPORT",
404
+ help="Read the counts from this feasibility report's frontmatter; a missing count fails.",
405
+ )
219
406
  return parser
220
407
 
221
408
 
@@ -232,6 +419,15 @@ def _resolve_input(args):
232
419
  def main(argv=None):
233
420
  parser = _build_parser()
234
421
  args = parser.parse_args(argv)
422
+ if args.report is not None:
423
+ reader = load_reader()
424
+ if reader is None:
425
+ message = f"the shared report reader {SHARED_READER.name} is not installed beside this skill"
426
+ print(json.dumps(make_error(message, HELPER_MISSING), indent=2))
427
+ return 1
428
+ result, code = rollup_report(args.report, reader)
429
+ print(json.dumps(result, indent=2))
430
+ return code
235
431
  raw = _resolve_input(args)
236
432
  if not raw.strip():
237
433
  parser.print_usage(file=sys.stderr)
@@ -254,5 +450,22 @@ def main(argv=None):
254
450
  return 0
255
451
 
256
452
 
453
+ def _force_utf8(*streams) -> None:
454
+ """Reconfigure the given streams to UTF-8, keeping each stream's error handler.
455
+
456
+ A Windows console pipes them as cp1252, which cannot print every character
457
+ of the --help text this module docstring supplies, and which garbles UTF-8
458
+ JSON piped into --stdin.
459
+ """
460
+ for stream in streams:
461
+ if hasattr(stream, "reconfigure"):
462
+ errors = getattr(stream, "errors", None)
463
+ if errors is None:
464
+ stream.reconfigure(encoding="utf-8")
465
+ else:
466
+ stream.reconfigure(encoding="utf-8", errors=errors)
467
+
468
+
257
469
  if __name__ == "__main__":
470
+ _force_utf8(sys.stdin, sys.stdout, sys.stderr)
258
471
  raise SystemExit(main())
@@ -1,19 +1,23 @@
1
1
  const chalk = require('chalk');
2
2
  const { Installer } = require('../lib/installer');
3
+ const { startToolCheck } = require('../lib/tool-check');
3
4
  const { UI } = require('../lib/ui');
4
5
 
5
6
  module.exports = {
6
7
  command: 'install',
7
8
  description: 'Install SKF skills into your project',
8
9
  options: [],
10
+ // Returns rather than exiting, so skf-cli.js prints the update notice after
11
+ // it; a failure sets process.exitCode.
9
12
  action: async () => {
13
+ // The tool probes run while the user answers the prompts and the files copy.
14
+ const printToolReport = startToolCheck();
10
15
  try {
11
16
  const ui = new UI();
12
17
  const config = await ui.promptInstall();
13
18
 
14
19
  if (config.cancelled) {
15
20
  console.log(chalk.yellow('\nInstallation cancelled.'));
16
- process.exit(0);
17
21
  return;
18
22
  }
19
23
 
@@ -22,15 +26,18 @@ module.exports = {
22
26
 
23
27
  if (result && result.success) {
24
28
  ui.displaySuccess(config.skfFolder, config.ides, config._action);
25
- process.exit(0);
29
+ await printToolReport();
26
30
  } else {
27
31
  console.error(chalk.red('\nInstallation failed.'));
28
- process.exit(1);
32
+ process.exitCode = 1;
29
33
  }
30
34
  } catch (error) {
31
35
  console.error(chalk.red('\nInstallation failed:'), error.message);
32
36
  console.error(chalk.dim(error.stack));
33
- process.exit(1);
37
+ process.exitCode = 1;
38
+ } finally {
39
+ // A cancelled or failed install prints no report: stop the probes still running.
40
+ printToolReport.stop();
34
41
  }
35
42
  },
36
43
  };
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * SKF Status Command
3
- * Shows installation state, version, tier, configured IDEs, and sidecar status.
3
+ * Shows installation state, version, tier, configured IDEs, and sidecar status,
4
+ * and under the tier the last setup recorded, the tools installed now.
4
5
  */
5
6
 
6
7
  const chalk = require('chalk');
@@ -9,6 +10,7 @@ const fs = require('fs-extra');
9
10
  const yaml = require('js-yaml');
10
11
  const { readManifest } = require('../lib/manifest');
11
12
  const { getAvailablePlatforms } = require('../lib/ide-skills');
13
+ const { startToolCheck } = require('../lib/tool-check');
12
14
 
13
15
  const SKF_FOLDER = '_bmad/skf';
14
16
  const SIDECAR_FOLDER = '_bmad/_memory/forger-sidecar';
@@ -94,7 +96,11 @@ async function getStatus(projectDir) {
94
96
  };
95
97
  }
96
98
 
97
- function displayStatus(status, version) {
99
+ /**
100
+ * Print the status. `printToolReport` prints what is installed now, below
101
+ * the tier and tools the last setup recorded.
102
+ */
103
+ async function displayStatus(status, version, printToolReport) {
98
104
  console.log('');
99
105
  console.log(chalk.hex('#F59E0B').bold(' Skill Forge — Status'));
100
106
  console.log(chalk.dim(` v${version}`));
@@ -104,6 +110,7 @@ function displayStatus(status, version) {
104
110
  console.log(chalk.yellow(' Not installed.'));
105
111
  console.log(chalk.dim(' Run: npx bmad-module-skill-forge install'));
106
112
  console.log('');
113
+ await printToolReport();
107
114
  return;
108
115
  }
109
116
 
@@ -157,6 +164,8 @@ function displayStatus(status, version) {
157
164
  console.log(` QMD: ${formatTool(tools.qmd)}`);
158
165
  console.log('');
159
166
 
167
+ await printToolReport();
168
+
160
169
  // Output Folders
161
170
  console.log(chalk.white.bold(' Output Folders'));
162
171
  console.log(` Skills: ${status.skillsFolder}/ ${status.skillsFolderExists ? chalk.green('✓') : chalk.yellow('missing')}`);
@@ -181,12 +190,15 @@ module.exports = {
181
190
  description: 'Show SKF installation state, version, tier, and configuration',
182
191
  options: [],
183
192
  action: async () => {
193
+ const printToolReport = startToolCheck();
184
194
  try {
185
195
  const projectDir = process.cwd();
186
196
  const packageJson = require('../../../package.json');
187
197
  const status = await getStatus(projectDir);
188
- displayStatus(status, packageJson.version);
198
+ await displayStatus(status, packageJson.version, printToolReport);
189
199
  } catch (error) {
200
+ // No report follows: stop the probes still running, so none outlives the exit.
201
+ printToolReport.stop();
190
202
  console.error(chalk.red('\nFailed to read status:'), error.message);
191
203
  process.exit(1);
192
204
  }
@@ -9,6 +9,7 @@ const path = require('node:path');
9
9
  const fs = require('fs-extra');
10
10
  const yaml = require('js-yaml');
11
11
  const { Installer } = require('../lib/installer');
12
+ const { startToolCheck } = require('../lib/tool-check');
12
13
  const { UI } = require('../lib/ui');
13
14
 
14
15
  const SKF_FOLDER = '_bmad/skf';
@@ -17,7 +18,11 @@ module.exports = {
17
18
  command: 'update',
18
19
  description: 'Update SKF files and reinstall agent skill (preserves config and sidecar)',
19
20
  options: [],
21
+ // Returns rather than exiting, so skf-cli.js prints the update notice after
22
+ // it; a failure sets process.exitCode.
20
23
  action: async () => {
24
+ // The tool probes run while the files copy.
25
+ const printToolReport = startToolCheck();
21
26
  try {
22
27
  const projectDir = process.cwd();
23
28
  const skfDir = path.join(projectDir, SKF_FOLDER);
@@ -25,7 +30,6 @@ module.exports = {
25
30
  if (!(await fs.pathExists(skfDir))) {
26
31
  console.log(chalk.yellow('\n SKF is not installed in this directory.'));
27
32
  console.log(chalk.dim(' Run: npx bmad-module-skill-forge install\n'));
28
- process.exit(0);
29
33
  return;
30
34
  }
31
35
 
@@ -52,14 +56,17 @@ module.exports = {
52
56
  }
53
57
  const ui = new UI();
54
58
  ui.displaySuccess(SKF_FOLDER, ides, 'update');
55
- process.exit(0);
59
+ await printToolReport();
56
60
  } else {
57
61
  console.error(chalk.red('\nUpdate failed.'));
58
- process.exit(1);
62
+ process.exitCode = 1;
59
63
  }
60
64
  } catch (error) {
61
65
  console.error(chalk.red('\nUpdate failed:'), error.message);
62
- process.exit(1);
66
+ process.exitCode = 1;
67
+ } finally {
68
+ // An update that fails, or finds no SKF here, prints no report: stop the probes still running.
69
+ printToolReport.stop();
63
70
  }
64
71
  },
65
72
  };