bmad-module-skill-forge 2.2.0 → 4.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 (437) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +49 -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 +143 -18
  18. package/docs/verifying-a-skill.md +87 -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 +3 -3
  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 +31 -59
  40. package/src/shared/references/description-guard-protocol.md +12 -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 +233 -64
  64. package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
  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 +772 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
  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 +21 -3
  74. package/src/shared/scripts/skf-description-guard.py +99 -11
  75. package/src/shared/scripts/skf-detect-docs.py +694 -2
  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 +197 -55
  79. package/src/shared/scripts/skf-detect-tools.py +209 -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 +5247 -96
  86. package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +664 -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 +469 -205
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +700 -0
  94. package/src/shared/scripts/skf-hash-content.py +180 -20
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +161 -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 +143 -20
  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 +176 -28
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
  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 +155 -14
  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 +787 -121
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +120 -67
  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 +23 -2
  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 +3945 -152
  138. package/src/shared/scripts/skf-write-skill-brief.py +406 -25
  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 +28 -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/health-check.md +2 -0
  168. package/src/skf-audit-skill/references/init.md +149 -210
  169. package/src/skf-audit-skill/references/re-index.md +103 -110
  170. package/src/skf-audit-skill/references/report.md +96 -33
  171. package/src/skf-audit-skill/references/semantic-diff.md +34 -25
  172. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  173. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  174. package/src/skf-audit-skill/references/structural-diff.md +80 -90
  175. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  176. package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
  177. package/src/skf-brief-skill/SKILL.md +37 -26
  178. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  179. package/src/skf-brief-skill/assets/scope-templates.md +18 -25
  180. package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
  181. package/src/skf-brief-skill/customize.toml +48 -20
  182. package/src/skf-brief-skill/references/analyze-target.md +144 -127
  183. package/src/skf-brief-skill/references/confirm-brief.md +11 -9
  184. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  185. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  186. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  187. package/src/skf-brief-skill/references/headless-args.md +84 -16
  188. package/src/skf-brief-skill/references/health-check.md +1 -1
  189. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  190. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  191. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  192. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  193. package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
  194. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  195. package/src/skf-brief-skill/references/version-resolution.md +7 -10
  196. package/src/skf-brief-skill/references/write-brief.md +66 -57
  197. package/src/skf-campaign/SKILL.md +31 -34
  198. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  199. package/src/skf-campaign/customize.toml +47 -16
  200. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  201. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  202. package/src/skf-campaign/references/health-check.md +7 -16
  203. package/src/skf-campaign/references/step-01-setup.md +46 -84
  204. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  205. package/src/skf-campaign/references/step-03-pins.md +26 -18
  206. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  207. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  208. package/src/skf-campaign/references/step-06-batch.md +41 -34
  209. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  210. package/src/skf-campaign/references/step-08-verify.md +28 -29
  211. package/src/skf-campaign/references/step-09-refine.md +29 -30
  212. package/src/skf-campaign/references/step-10-export.md +48 -36
  213. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  214. package/src/skf-campaign/references/step-resume.md +37 -68
  215. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  216. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  217. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  218. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  219. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  220. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  221. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  222. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  223. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  224. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  225. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  226. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  227. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  228. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  229. package/src/skf-create-skill/SKILL.md +27 -20
  230. package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
  231. package/src/skf-create-skill/assets/skill-sections.md +21 -11
  232. package/src/skf-create-skill/customize.toml +20 -8
  233. package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
  234. package/src/skf-create-skill/references/batch-mode.md +81 -0
  235. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  236. package/src/skf-create-skill/references/compile.md +119 -70
  237. package/src/skf-create-skill/references/component-extraction.md +113 -91
  238. package/src/skf-create-skill/references/enrich.md +41 -23
  239. package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
  240. package/src/skf-create-skill/references/extract.md +204 -162
  241. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  242. package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
  243. package/src/skf-create-skill/references/extraction-patterns.md +588 -201
  244. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  245. package/src/skf-create-skill/references/health-check.md +1 -1
  246. package/src/skf-create-skill/references/load-brief.md +53 -36
  247. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  248. package/src/skf-create-skill/references/report.md +37 -57
  249. package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
  250. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  251. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  252. package/src/skf-create-skill/references/step-doc-sources.md +28 -19
  253. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  254. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
  255. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  256. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  257. package/src/skf-create-skill/references/validate.md +173 -99
  258. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  259. package/src/skf-create-stack-skill/SKILL.md +29 -56
  260. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  261. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  262. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  263. package/src/skf-create-stack-skill/customize.toml +23 -12
  264. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  265. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  266. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  267. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  268. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  269. package/src/skf-create-stack-skill/references/init.md +90 -32
  270. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  271. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  272. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  273. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  274. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  275. package/src/skf-create-stack-skill/references/report.md +39 -33
  276. package/src/skf-create-stack-skill/references/validate.md +20 -39
  277. package/src/skf-drop-skill/SKILL.md +28 -51
  278. package/src/skf-drop-skill/customize.toml +44 -30
  279. package/src/skf-drop-skill/references/execute.md +83 -146
  280. package/src/skf-drop-skill/references/health-check.md +2 -0
  281. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  282. package/src/skf-drop-skill/references/report.md +46 -30
  283. package/src/skf-drop-skill/references/select.md +122 -124
  284. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  285. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  286. package/src/skf-export-skill/SKILL.md +40 -62
  287. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  288. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  289. package/src/skf-export-skill/customize.toml +42 -13
  290. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  291. package/src/skf-export-skill/references/health-check.md +2 -0
  292. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  293. package/src/skf-export-skill/references/load-skill.md +88 -62
  294. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  295. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  296. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  297. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  298. package/src/skf-export-skill/references/package.md +15 -73
  299. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  300. package/src/skf-export-skill/references/result-envelope.md +31 -16
  301. package/src/skf-export-skill/references/summary.md +41 -17
  302. package/src/skf-export-skill/references/token-report.md +4 -12
  303. package/src/skf-export-skill/references/update-context.md +118 -237
  304. package/src/skf-forger/SKILL.md +61 -37
  305. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  306. package/src/skf-forger/customize.toml +43 -0
  307. package/src/skf-forger/references/pipeline-mode.md +73 -18
  308. package/src/skf-forger/scripts/forge-status.py +299 -0
  309. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  310. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  311. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  312. package/src/skf-quick-skill/SKILL.md +54 -53
  313. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  314. package/src/skf-quick-skill/customize.toml +51 -20
  315. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  316. package/src/skf-quick-skill/references/compile.md +30 -31
  317. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  318. package/src/skf-quick-skill/references/finalize.md +69 -18
  319. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  320. package/src/skf-quick-skill/references/health-check.md +5 -3
  321. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  322. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  323. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  324. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  325. package/src/skf-refine-architecture/SKILL.md +43 -53
  326. package/src/skf-refine-architecture/customize.toml +49 -20
  327. package/src/skf-refine-architecture/references/compile.md +97 -54
  328. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  329. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  330. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  331. package/src/skf-refine-architecture/references/improvements.md +14 -20
  332. package/src/skf-refine-architecture/references/init.md +98 -57
  333. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  334. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  335. package/src/skf-refine-architecture/references/report.md +65 -54
  336. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  337. package/src/skf-rename-skill/SKILL.md +19 -54
  338. package/src/skf-rename-skill/customize.toml +32 -26
  339. package/src/skf-rename-skill/references/execute.md +230 -184
  340. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  341. package/src/skf-rename-skill/references/health-check.md +1 -7
  342. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  343. package/src/skf-rename-skill/references/report.md +63 -52
  344. package/src/skf-rename-skill/references/select.md +197 -115
  345. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  346. package/src/skf-setup/SKILL.md +26 -26
  347. package/src/skf-setup/customize.toml +14 -10
  348. package/src/skf-setup/references/auto-index.md +50 -63
  349. package/src/skf-setup/references/ccc-index.md +29 -90
  350. package/src/skf-setup/references/detect-and-tier.md +58 -67
  351. package/src/skf-setup/references/health-check.md +2 -0
  352. package/src/skf-setup/references/invocation-contract.md +14 -0
  353. package/src/skf-setup/references/report.md +43 -166
  354. package/src/skf-setup/references/tier-rules.md +5 -5
  355. package/src/skf-setup/references/write-config.md +34 -57
  356. package/src/skf-test-skill/SKILL.md +17 -57
  357. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  358. package/src/skf-test-skill/customize.toml +27 -13
  359. package/src/skf-test-skill/references/coherence-check.md +134 -62
  360. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  361. package/src/skf-test-skill/references/coverage-check.md +209 -264
  362. package/src/skf-test-skill/references/detect-mode.md +5 -5
  363. package/src/skf-test-skill/references/external-validators.md +94 -82
  364. package/src/skf-test-skill/references/health-check.md +6 -7
  365. package/src/skf-test-skill/references/init.md +164 -91
  366. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  367. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  368. package/src/skf-test-skill/references/report.md +164 -142
  369. package/src/skf-test-skill/references/score.md +80 -107
  370. package/src/skf-test-skill/references/scoring-rules.md +42 -155
  371. package/src/skf-test-skill/references/source-access-protocol.md +18 -94
  372. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  373. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  374. package/src/skf-test-skill/scripts/build-result-context.py +380 -0
  375. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  376. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  377. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  378. package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
  379. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  380. package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
  381. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  382. package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
  383. package/src/skf-test-skill/scripts/score-signatures.py +479 -0
  384. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  385. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  386. package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
  387. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  388. package/src/skf-update-skill/SKILL.md +38 -25
  389. package/src/skf-update-skill/customize.toml +27 -12
  390. package/src/skf-update-skill/references/detect-changes.md +253 -223
  391. package/src/skf-update-skill/references/gap-driven.md +336 -0
  392. package/src/skf-update-skill/references/health-check.md +31 -5
  393. package/src/skf-update-skill/references/init.md +254 -84
  394. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  395. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  396. package/src/skf-update-skill/references/merge.md +182 -49
  397. package/src/skf-update-skill/references/re-extract.md +53 -262
  398. package/src/skf-update-skill/references/report.md +102 -35
  399. package/src/skf-update-skill/references/write.md +357 -155
  400. package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
  401. package/src/skf-verify-stack/SKILL.md +23 -42
  402. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  403. package/src/skf-verify-stack/customize.toml +55 -22
  404. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  405. package/src/skf-verify-stack/references/coverage.md +67 -64
  406. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  407. package/src/skf-verify-stack/references/init.md +107 -62
  408. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  409. package/src/skf-verify-stack/references/integrations.md +106 -65
  410. package/src/skf-verify-stack/references/report.md +59 -42
  411. package/src/skf-verify-stack/references/requirements.md +32 -9
  412. package/src/skf-verify-stack/references/synthesize.md +53 -39
  413. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  414. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  415. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  416. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  417. package/tools/cli/commands/install.js +11 -4
  418. package/tools/cli/commands/status.js +15 -3
  419. package/tools/cli/commands/update.js +11 -4
  420. package/tools/cli/lib/tool-check.js +320 -0
  421. package/tools/cli/lib/version-check.js +22 -9
  422. package/tools/cli/skf-cli.js +4 -1
  423. package/.gitattributes +0 -16
  424. package/.nvmrc +0 -1
  425. package/docs/_internal/RELEASING.md +0 -679
  426. package/release-audits/v1.0.0-launch-audit.md +0 -728
  427. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  428. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  429. package/src/skf-campaign/manifest.yaml +0 -15
  430. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  431. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  432. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  433. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  434. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  435. package/src/skf-update-skill/references/validate.md +0 -124
  436. package/tools/validate-docs-drift.js +0 -233
  437. package/tools/validate-docs-links.js +0 -665
@@ -1,55 +1,74 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.11"
4
4
  # ///
5
- """Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2).
6
-
7
- step 5c self-declares its correction scan "grep-based and deterministic — no AI
8
- judgment is used for detection." This helper *is* that grep: it walks the
9
- resolved feeder artifacts, matches every line against the fixed 13-row
10
- correction-pattern table with case-insensitive substring containment (no regex,
11
- no semantics), and emits the matches as JSON. It applies the positional filters
12
- the step documents — dropping matches that land inside the compiled SKILL.md's
13
- own YAML frontmatter or its own `## Migration & Deprecation Warnings` section
14
- (both are self-authored: compile §2 wrote the frontmatter `description` and
15
- compile §4b wrote the migration bullets, so re-emitting either would be
16
- circular) — and then bounds what survives, before it returns. Running the scan
17
- here (instead of in-prompt) makes the "deterministic, identical input →
18
- identical output" promise actually hold: the model no longer hand-greps
19
- multi-KB artifacts.
20
-
21
- Bounding matters because §2's own example command passes the raw temporal
22
- changelog as a feeder. That artifact is a verbatim upstream dump (thousands of
23
- lines of release history), so an unbounded run turns years of "breaking" /
24
- "deprecated" lines into hundreds of `## CORRECTION` blocks and blows the
25
- compiled body's line budget. Two deterministic bounds run after the exclusions:
26
- duplicate collapse (same category + same normalized line text) and a hard cap
27
- on how many blocks a single run may propose. Nothing is silently destroyed —
28
- a collapsed record carries `occurrences` and `duplicate_of`, and the counts of
29
- what was collapsed and capped are reported so the step can log them.
30
-
31
- The genuine judgment the step keeps in-prompt is untouched by this script:
32
- enriching each match's `affected` symbol from surrounding context (§2) and
33
- choosing where the `## CORRECTION` block goes (§3). The script emits every
34
- deterministic field (`source`, `pattern`, `category`, `context_line`,
35
- `line_number`); the prompt adds `affected`.
36
-
37
- The pattern table and category labels are the contract in step-doc-rot.md §2 —
38
- keep the two in lockstep.
5
+ """Doc-rot correction-candidate scan (step-doc-rot.md §2).
6
+
7
+ Step 5c writes a `## CORRECTION` block for each live upstream correction to an
8
+ export the compiled skill documents. This helper finds the candidates: it
9
+ walks the resolved feeder artifacts, matches every line against the fixed
10
+ 13-row keyword table with case-insensitive substring containment (no regex,
11
+ no semantics), and emits each hit with the kind of change its keyword
12
+ suggests (`candidate_category`), a ranking hint. A keyword is not a
13
+ correction: "No breaking changes in this release" holds one, and so do a pull
14
+ request template checkbox and a bug fix that mentions a deprecation warning.
15
+ Nor is every correction worded with one: a Keep a Changelog "### Removed"
16
+ bullet that names `Client.close_all`, or "Renamed `load` to `read`", holds
17
+ none. So with --provenance (the staged provenance map), a line of the
18
+ project's own announcements (the temporal feeder's changelog.md,
19
+ releases.md and prs.md) that names an export the map lists is a candidate
20
+ too. Every candidate carries the nearest headings above it: `release`, the
21
+ nearest one naming a version (`## [2.3.0] - 2024-05-01`, `## v2.3.0`), and
22
+ `section`, the nearest one under that (`### Removed`). The step's judgment
23
+ pass decides which candidates are live corrections, then caps the blocks at
24
+ 10. Running the scan here (instead of in-prompt) keeps the candidate list
25
+ identical for identical feeders: the model never hand-greps multi-KB
26
+ artifacts.
27
+
28
+ Before it returns, the script drops matches that land inside the compiled
29
+ SKILL.md's own YAML frontmatter or its own `## Migration & Deprecation
30
+ Warnings` section (both are self-authored: compile §2 wrote the frontmatter
31
+ `description` and compile §4b wrote the migration bullets, so re-emitting
32
+ either would be circular), and then bounds what survives.
33
+
34
+ Bounding matters because the temporal feeder is a verbatim upstream dump
35
+ (changelog, release notes, issue and pull request bodies), so an unbounded
36
+ run can hand the judgment pass hundreds of "breaking" and "deprecated" lines.
37
+ Two deterministic bounds run after the exclusions: duplicate collapse (same
38
+ candidate category + same normalized line text) and a cap on the candidate
39
+ pool. Keyword hits, and export mentions under a change section, come first;
40
+ a plain export mention (an "### Added" bullet) gets only the places left. A
41
+ change section is a heading whose whole text is a change name (Keep a
42
+ Changelog's `### Deprecated`, `### Removed` and `### Changed`, `## Breaking
43
+ Changes`, `## Migration Guide`), never one with such a word inside it:
44
+ GitHub's generated `## What's Changed` and a pull request template's
45
+ `## Type of change` head every line of their feeder. Within each of those two
46
+ ranks the feeders share the cap in turns, so a years-deep changelog cannot
47
+ crowd out the release notes, and the compiled SKILL.md's own lines of a rank
48
+ come after every other feeder's lines of that rank, so the skill's own text
49
+ never outranks an upstream line of the same rank.
50
+ Nothing is silently destroyed: a collapsed record carries `occurrences` and
51
+ `duplicate_of`, and the counts of what was collapsed and capped are reported
52
+ so the step can log them.
39
53
 
40
54
  CLI usage:
41
- uv run scan-doc-rot.py --skill-md <staged SKILL.md> [FEEDER ...]
55
+ uv run scan-doc-rot.py --skill-md <staged SKILL.md> [--provenance <map>] [FEEDER ...]
42
56
  uv run scan-doc-rot.py --feeder evidence-report.md --feeder provenance-map.json
43
57
 
44
58
  --skill-md the compiled/staged SKILL.md feeder (feeder #4); matches
45
59
  inside its YAML frontmatter or its `## Migration &
46
- Deprecation Warnings` section are excluded. It is also
60
+ Deprecation Warnings` section are excluded, and its other
61
+ candidates come last when the pool is capped. It is also
47
62
  scanned like any other feeder.
48
63
  FEEDER any other feeder artifact (evidence-report.md,
49
64
  provenance-map.json, temporal-context files). Repeatable
50
65
  positionally or via --feeder.
51
- --max-corrections cap on emitted matches (default 10). 0 or negative means
52
- unlimited.
66
+ --provenance the staged provenance-map.json: its entries[].export_name
67
+ values are the exports a changelog.md, releases.md or
68
+ prs.md line may name (`default` aside). Missing, empty or
69
+ unreadable: no export is known, as without the flag.
70
+ --max-candidates cap on emitted candidates (default 50). 0 or negative
71
+ means unlimited.
53
72
 
54
73
  Missing or empty files are skipped silently (not an error), matching §1's
55
74
  "attempt to load; if it does not exist or is empty, skip it."
@@ -57,9 +76,13 @@ CLI usage:
57
76
  Output (stdout, one object):
58
77
  {
59
78
  "scanned": ["<path>", ...], # feeders that existed and were non-empty
60
- "matches": [ # correction_matches[] (affected added in-prompt)
61
- {"source": "<path>", "pattern": "deprecated", "category": "Deprecation",
79
+ "matches": [ # the candidates step 5c's judgment pass reviews
80
+ {"source": "<path>", "pattern": "deprecated" | null, # null: an export mention
81
+ "candidate_category": "Deprecation" | null,
62
82
  "context_line": "<line text>", "line_number": <1-indexed int>,
83
+ "exports": ["<export_name>", ...], # the known exports the line names
84
+ "release": "<heading>" | null, # nearest heading above naming a version
85
+ "section": "<heading>" | null, # nearest heading below that one
63
86
  "occurrences": <int>, # 1 unless duplicates collapsed into this record
64
87
  "duplicate_of": [{"source": "<path>", "line_number": <int>}, ...]},
65
88
  ...
@@ -67,8 +90,9 @@ Output (stdout, one object):
67
90
  "match_count": <int>, # len(matches), after exclusions/collapse/cap
68
91
  "excluded_count": <int>, # SKILL.md matches dropped (frontmatter + §4b)
69
92
  "deduped_count": <int>, # matches collapsed into a surviving record
70
- "capped_count": <int>, # matches dropped because the cap was reached
71
- "cap": <int> # effective cap (0 = unlimited)
93
+ "capped_count": <int>, # candidates dropped because the cap was reached
94
+ "cap": <int>, # effective cap (0 = unlimited)
95
+ "exports_known": <int> # export names --provenance gave
72
96
  }
73
97
 
74
98
  Exit codes:
@@ -79,12 +103,14 @@ Exit codes:
79
103
  from __future__ import annotations
80
104
 
81
105
  import argparse
106
+ import itertools
82
107
  import json
83
108
  import re
84
109
  import sys
85
110
  from pathlib import Path
86
111
 
87
- # Fixed correction-pattern table — mirrors step-doc-rot.md §2. (pattern, category)
112
+ # Fixed keyword table: (pattern, candidate_category). A hit makes the line a
113
+ # candidate for step-doc-rot.md §2's judgment pass, never a correction by itself.
88
114
  # Order is the table order; scanning is per-pattern so overlapping patterns
89
115
  # (e.g. "deprecated" ⊂ "@deprecated") each record their own hit, exactly as the
90
116
  # 13-row table enumerates them.
@@ -106,32 +132,83 @@ PATTERN_TABLE: list[tuple[str, str]] = [
106
132
 
107
133
  _MIGRATION_HEADING = re.compile(r"^\s*##\s+Migration\s*&\s*Deprecation Warnings", re.IGNORECASE)
108
134
 
109
- # Cap on how many `## CORRECTION` blocks one run may propose (step-doc-rot.md §3).
110
- # Step 5b budgets the compiled body at 400 lines and each block is ~7 lines, so
111
- # 10 blocks stays inside the budget with headroom.
112
- DEFAULT_MAX_CORRECTIONS = 10
113
-
114
-
115
- def scan_text(text: str, source: str) -> list[dict]:
135
+ # The temporal feeder files that carry the project's own announcements: a line
136
+ # of one that names a known export is a candidate (issues.md and
137
+ # targeted-issues.md hold reports anyone can open, which §2 drops anyway).
138
+ EXPORT_FEEDERS = frozenset({"changelog.md", "releases.md", "prs.md"})
139
+ # An export name too common to mean the export when a line says it.
140
+ IGNORED_EXPORT_NAMES = frozenset({"default"})
141
+ _HEADING = re.compile(r"^(#{1,6})\s+(.*?)\s*#*\s*$")
142
+ _FENCE = re.compile(r"^\s*(```|~~~)")
143
+ # A heading that names a release: a version (`[2.3.0]`, `v2.3.0`, `2.3`) or Unreleased.
144
+ _RELEASE_HEADING = re.compile(r"\bv?\d+\.\d+|\bunreleased\b", re.IGNORECASE)
145
+ # A change section: a heading whose whole text, its marks stripped, is one of
146
+ # these names. Its export mentions rank with the keyword hits.
147
+ _CHANGE_SECTION = re.compile(
148
+ r"deprecated|deprecations?|removed|removals?|changed|breaking(?: changes?)?|migrations?(?: guide)?",
149
+ re.IGNORECASE,
150
+ )
151
+ # The marks around a heading's words: `[Removed]`, `Removed:`, `⚠ BREAKING CHANGES`.
152
+ _HEADING_MARKS = re.compile(r"^[\W_]+|[\W_]+$")
153
+ _IDENTIFIER = re.compile(r"[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*")
154
+
155
+ # Cap on the candidate pool one run hands the judgment pass (step-doc-rot.md §2).
156
+ # The pass keeps at most 10 corrections (step 5b budgets the compiled body at 400
157
+ # lines and each block is ~7 lines), so 50 candidates leaves room for the many
158
+ # keyword lines that are no correction at all.
159
+ DEFAULT_MAX_CANDIDATES = 50
160
+
161
+
162
+ def named_exports(line: str, exports: frozenset[str]) -> list[str]:
163
+ """The known export names `line` names: each dotted identifier on it
164
+ (`Client.close_all`) and each of its parts (`close_all`), checked
165
+ against the set. Sorted, each once."""
166
+ found: set[str] = set()
167
+ for token in _IDENTIFIER.findall(line):
168
+ for name in (token, *token.split(".")):
169
+ if name in exports:
170
+ found.add(name)
171
+ return sorted(found)
172
+
173
+
174
+ def scan_text(
175
+ text: str, source: str, exports: frozenset[str] = frozenset(), export_feeder: bool = False
176
+ ) -> list[dict]:
116
177
  """Case-insensitive substring scan of `text` against PATTERN_TABLE.
117
178
 
118
- Returns one record per (line, pattern) hit, in file order then table order.
119
- line_number is 1-indexed. Pure — no I/O.
179
+ Returns one record per (line, pattern) hit, in file order then table
180
+ order, and, when `export_feeder`, one record for a line with no hit that
181
+ names an export of `exports` (pattern and candidate_category null).
182
+ Every record carries the line's `exports` and the nearest `release` and
183
+ `section` headings above it (headings inside a code fence do not count).
184
+ line_number is 1-indexed. Pure: no I/O.
120
185
  """
121
186
  matches: list[dict] = []
187
+ release: str | None = None
188
+ section: str | None = None
189
+ fenced = False
122
190
  for idx, line in enumerate(text.splitlines(), start=1):
191
+ if _FENCE.match(line):
192
+ fenced = not fenced
193
+ heading = None if fenced else _HEADING.match(line)
194
+ if heading:
195
+ title = line.strip()
196
+ if _RELEASE_HEADING.search(heading.group(2)):
197
+ release, section = title, None
198
+ else:
199
+ section = title
200
+ named = named_exports(line, exports) if exports else []
123
201
  lowered = line.lower()
124
- for pattern, category in PATTERN_TABLE:
202
+ context = {"context_line": line.strip(), "line_number": idx, "exports": named,
203
+ "release": release, "section": None if heading else section}
204
+ hit = False
205
+ for pattern, candidate_category in PATTERN_TABLE:
125
206
  if pattern.lower() in lowered:
126
- matches.append(
127
- {
128
- "source": source,
129
- "pattern": pattern,
130
- "category": category,
131
- "context_line": line.strip(),
132
- "line_number": idx,
133
- }
134
- )
207
+ hit = True
208
+ matches.append({"source": source, "pattern": pattern,
209
+ "candidate_category": candidate_category, **context})
210
+ if not hit and export_feeder and named:
211
+ matches.append({"source": source, "pattern": None, "candidate_category": None, **context})
135
212
  return matches
136
213
 
137
214
 
@@ -240,23 +317,25 @@ def apply_migration_exclusion(
240
317
 
241
318
 
242
319
  def collapse_duplicates(matches: list[dict]) -> tuple[list[dict], int]:
243
- """Collapse matches that repeat the same text under the same category.
320
+ """Collapse matches that repeat the same text under the same candidate category.
244
321
 
245
322
  Returns (kept, collapsed). The dedup key is
246
- `(category, whitespace-normalized lowercased context_line)` — a temporal
247
- changelog restates the same deprecation across many releases, and each
248
- restatement would otherwise become its own `## CORRECTION` block saying the
249
- same thing.
323
+ `(candidate_category, whitespace-normalized lowercased context_line)`: a
324
+ temporal changelog restates the same deprecation across many releases, and
325
+ a pull request template repeats the same checkbox in every pull request, so
326
+ each restatement would otherwise be its own candidate saying the same thing.
250
327
 
251
328
  The first occurrence in scan order survives and gains two fields:
252
- `occurrences` (how many lines collapsed into it, including itself) and
253
- `duplicate_of` (the `{source, line_number}` of every later occurrence), so
254
- nothing is silently destroyed. Records are copies — the inputs are untouched.
329
+ `occurrences` (how many keyword hits collapsed into it, itself included)
330
+ and `duplicate_of` (the `{source, line_number}` of every later hit), so
331
+ nothing is silently destroyed. Two keywords of one candidate category on
332
+ one line count twice, and the second hit points back at the same line.
333
+ Records are copies: the inputs are untouched.
255
334
  """
256
335
  seen: dict[tuple[str, str], dict] = {}
257
336
  kept: list[dict] = []
258
337
  for m in matches:
259
- key = (m["category"], " ".join(m["context_line"].split()).lower())
338
+ key = (m["candidate_category"], " ".join(m["context_line"].split()).lower())
260
339
  survivor = seen.get(key)
261
340
  if survivor is not None:
262
341
  survivor["occurrences"] += 1
@@ -270,24 +349,50 @@ def collapse_duplicates(matches: list[dict]) -> tuple[list[dict], int]:
270
349
  return kept, len(matches) - len(kept)
271
350
 
272
351
 
352
+ def change_section(section: str | None) -> bool:
353
+ """True when `section` (a heading line) is a change section: its text,
354
+ without its `#`s and the marks around its words, is a change name as a
355
+ whole. `### Removed` and `## Breaking Changes` are; `## What's Changed`
356
+ and `## Type of change` are not."""
357
+ title = _HEADING_MARKS.sub("", (section or "").strip().lstrip("#"))
358
+ return bool(_CHANGE_SECTION.fullmatch(title))
359
+
360
+
361
+ def rank(match: dict) -> int:
362
+ """0 for a keyword hit or a line under a change section, 1 for a plain
363
+ export mention (an "### Added" bullet)."""
364
+ if match.get("pattern") is not None:
365
+ return 0
366
+ return 0 if change_section(match.get("section")) else 1
367
+
368
+
273
369
  def apply_cap(
274
370
  matches: list[dict], cap: int, skill_md_source: str | None
275
371
  ) -> tuple[list[dict], int]:
276
- """Keep at most `cap` matches. Returns (kept, dropped).
277
-
278
- A cap of 0 or less means unlimited. Selection prefers the compiled SKILL.md's
279
- own annotations over other feeders — a naive head-slice would drop them first,
280
- because scan order puts the skill-md feeder last — and falls back to scan
281
- order within each group. The kept records are returned in scan order, so a
282
- run that does not hit the cap is ordered exactly as it is today.
372
+ """Keep at most `cap` candidates. Returns (kept, dropped).
373
+
374
+ A cap of 0 or less means unlimited. Rank 0 (keyword hits, and export
375
+ mentions under a change section) fills the cap before rank 1 (plain
376
+ export mentions). Within a rank the feeders share the cap in turns: each
377
+ turn takes the next candidate of every feeder, in scan order, so one long
378
+ feeder (a years-deep changelog) cannot crowd out the rest. The compiled
379
+ SKILL.md's own candidates take only the places of their rank left after
380
+ every other feeder's, because its body restates what compile wrote: the
381
+ skill's own text never outranks an upstream line of its rank. The kept records are
382
+ returned in scan order, so a run that does not hit the cap is ordered
383
+ exactly as the scan.
283
384
  """
284
385
  if cap <= 0 or len(matches) <= cap:
285
386
  return matches, 0
286
- priority = sorted(
287
- range(len(matches)),
288
- key=lambda i: (0 if matches[i]["source"] == skill_md_source else 1, i),
289
- )
290
- keep = sorted(priority[:cap])
387
+ order: list[int] = []
388
+ for level in (0, 1):
389
+ by_feeder: dict[str, list[int]] = {}
390
+ for i, m in enumerate(matches):
391
+ if m["source"] != skill_md_source and rank(m) == level:
392
+ by_feeder.setdefault(m["source"], []).append(i)
393
+ order += [i for turn in itertools.zip_longest(*by_feeder.values()) for i in turn if i is not None]
394
+ order += [i for i, m in enumerate(matches) if m["source"] == skill_md_source and rank(m) == level]
395
+ keep = sorted(order[:cap])
291
396
  return [matches[i] for i in keep], len(matches) - cap
292
397
 
293
398
 
@@ -303,18 +408,35 @@ def _load(path: str) -> str | None:
303
408
  return text
304
409
 
305
410
 
411
+ def load_exports(path: str | None) -> frozenset[str]:
412
+ """The entries[].export_name values of a provenance map; empty when the
413
+ path is None, missing, empty or not a provenance map."""
414
+ text = _load(path) if path else None
415
+ if text is None:
416
+ return frozenset()
417
+ try:
418
+ data = json.loads(text)
419
+ except ValueError:
420
+ return frozenset()
421
+ entries = data.get("entries") if isinstance(data, dict) else None
422
+ names = {e.get("export_name") for e in entries or [] if isinstance(e, dict)}
423
+ return frozenset(n for n in names if isinstance(n, str) and n and n not in IGNORED_EXPORT_NAMES)
424
+
425
+
306
426
  def scan_files(
307
427
  feeders: list[str],
308
428
  skill_md: str | None,
309
429
  *,
310
- max_corrections: int = DEFAULT_MAX_CORRECTIONS,
430
+ max_candidates: int = DEFAULT_MAX_CANDIDATES,
431
+ provenance: str | None = None,
311
432
  ) -> dict:
312
433
  """Scan every feeder + the skill-md feeder, apply the self-authorship
313
- exclusions, collapse duplicates, cap the survivors, and return the result
314
- object. Deterministic: feeders scanned in the given order."""
434
+ exclusions, collapse duplicates, cap the candidate pool, and return the
435
+ result object. Deterministic: feeders scanned in the given order."""
315
436
  scanned: list[str] = []
316
437
  matches: list[dict] = []
317
438
  skill_md_text = None
439
+ exports = load_exports(provenance)
318
440
 
319
441
  ordered = list(feeders)
320
442
  if skill_md is not None:
@@ -327,7 +449,8 @@ def scan_files(
327
449
  scanned.append(path)
328
450
  if path == skill_md:
329
451
  skill_md_text = text
330
- matches.extend(scan_text(text, path))
452
+ export_feeder = path != skill_md and Path(path).name in EXPORT_FEEDERS
453
+ matches.extend(scan_text(text, path, exports, export_feeder))
331
454
 
332
455
  # Frontmatter first, then the §4b section — the two windows are disjoint, so
333
456
  # the total is order-independent, but running them in file order keeps the
@@ -335,7 +458,7 @@ def scan_files(
335
458
  matches, front_excluded = apply_frontmatter_exclusion(matches, skill_md, skill_md_text)
336
459
  matches, section_excluded = apply_migration_exclusion(matches, skill_md, skill_md_text)
337
460
  matches, deduped = collapse_duplicates(matches)
338
- matches, capped = apply_cap(matches, max_corrections, skill_md)
461
+ matches, capped = apply_cap(matches, max_candidates, skill_md)
339
462
  return {
340
463
  "scanned": scanned,
341
464
  "matches": matches,
@@ -343,7 +466,8 @@ def scan_files(
343
466
  "excluded_count": front_excluded + section_excluded,
344
467
  "deduped_count": deduped,
345
468
  "capped_count": capped,
346
- "cap": max(max_corrections, 0),
469
+ "cap": max(max_candidates, 0),
470
+ "exports_known": len(exports),
347
471
  }
348
472
 
349
473
 
@@ -351,11 +475,12 @@ def _build_parser() -> argparse.ArgumentParser:
351
475
  parser = argparse.ArgumentParser(
352
476
  prog="scan-doc-rot",
353
477
  description=(
354
- "Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2): "
478
+ "Doc-rot correction-candidate scan (step-doc-rot.md §2): "
355
479
  "case-insensitive substring match of feeder artifacts against the fixed "
356
- "correction-pattern table, with the compiled SKILL.md's frontmatter and "
480
+ "keyword table, with the compiled SKILL.md's frontmatter and "
357
481
  "Migration & Deprecation Warnings section excluded, duplicates collapsed, "
358
- "and the survivors capped."
482
+ "and the candidate pool capped. The step's judgment pass decides which "
483
+ "candidates are corrections."
359
484
  ),
360
485
  formatter_class=argparse.RawDescriptionHelpFormatter,
361
486
  )
@@ -377,15 +502,23 @@ def _build_parser() -> argparse.ArgumentParser:
377
502
  dest="skill_md",
378
503
  default=None,
379
504
  help="Compiled/staged SKILL.md feeder; its frontmatter and its Migration & "
380
- "Deprecation Warnings section are excluded from matches.",
505
+ "Deprecation Warnings section are excluded from matches, and its other "
506
+ "candidates come last when the pool is capped.",
507
+ )
508
+ parser.add_argument(
509
+ "--provenance",
510
+ dest="provenance",
511
+ default=None,
512
+ help="The staged provenance-map.json: a changelog.md, releases.md or prs.md line "
513
+ "that names one of its entries[].export_name values is a candidate too.",
381
514
  )
382
515
  parser.add_argument(
383
- "--max-corrections",
384
- dest="max_corrections",
516
+ "--max-candidates",
517
+ dest="max_candidates",
385
518
  type=int,
386
- default=DEFAULT_MAX_CORRECTIONS,
519
+ default=DEFAULT_MAX_CANDIDATES,
387
520
  help=(
388
- f"Maximum matches to emit (default {DEFAULT_MAX_CORRECTIONS}). "
521
+ f"Maximum candidates to emit (default {DEFAULT_MAX_CANDIDATES}). "
389
522
  "0 or negative means unlimited."
390
523
  ),
391
524
  )
@@ -400,7 +533,7 @@ def main(argv=None) -> int:
400
533
  parser.print_usage(file=sys.stderr)
401
534
  print("error: supply at least one feeder path or --skill-md", file=sys.stderr)
402
535
  return 1
403
- result = scan_files(feeders, args.skill_md, max_corrections=args.max_corrections)
536
+ result = scan_files(feeders, args.skill_md, max_candidates=args.max_candidates, provenance=args.provenance)
404
537
  print(json.dumps(result, indent=2))
405
538
  return 0
406
539
 
@@ -11,9 +11,10 @@ Produces a consolidated stack skill documenting how libraries connect. **Code-mo
11
11
 
12
12
  ## Conventions
13
13
 
14
- - Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
15
- - The `knowledge/` and `shared/` prefixes are the exception: they resolve from the **SKF module root** (`{project-root}/_bmad/skf/` when installed, `src/` during development), not the skill root — they point at module-shared reference docs (`knowledge/tool-resolution.md`, `knowledge/version-paths.md`) and scripts/schemas (`shared/references/…`) that live once at the module root, mirroring the resolution note `references/health-check.md` carries for `shared/health-check.md`.
16
- - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
14
+ - Bare paths (e.g. `references/<name>.md`) resolve from the skill root, inside a `references/` file too: only a stage's frontmatter `nextStepFile` names a file beside it.
15
+ - **Module-level path exception:** bare paths beginning with `knowledge/` or `shared/` resolve from the SKF module root (`{project-root}/_bmad/skf/` installed, `src/` in dev), not the skill root; e.g. `knowledge/tool-resolution.md`, `knowledge/version-paths.md` and `shared/references/feasibility-report-schema.md`.
16
+ - **Sibling skills:** a path that names another SKF skill's folder (`skf-<name>/...`) resolves from the SKF module root, and that skill must be installed with this one.
17
+ - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `assets/` holds the stack skill template, the metadata contract and the provenance map schema.
17
18
  - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
18
19
  - `{project-root}`-prefixed paths resolve from the project working directory.
19
20
  - `{skill-name}` resolves to the skill directory's basename.
@@ -28,23 +29,33 @@ These rules apply to every step in this workflow:
28
29
 
29
30
  - Zero hallucination — all extracted content must trace to actual source code (compose-mode inferences must be labeled)
30
31
  - Only load one step file at a time — never preload future steps
31
- - If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread
32
+ - If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread, with two exceptions: never decide by hand whether SKF generated a folder (the ownership check), and never work out by hand what a shared helper computes where its step names a `helper-missing` HALT: that helper resolving to no path stops the run with exit 3 `helper-missing` (`references/invocation-contract.md` lists every such halt). Where a step says what to do without its helper (an advisory check, a default value), do as it says
33
+ - Never write into a skill folder SKF did not generate — generate-output §1 runs the inventory's write check before any prior metadata is read and again before staging
32
34
  - Always communicate in `{communication_language}`
33
- - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and log each auto-decision
34
- - Warnings use a single accumulator — see `## Workflow state contract` below for shape and surfacing.
35
+ - If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action, and record each auto-decision in the run sink the moment the gate takes it, with the `record --decision` command the gate shows
36
+ - Every HARD HALT names its exit code, `halt_reason` and phase, and prints its envelope through the shared emitter with the command its step file shows (the `init:emitter` halt, with no emitter, only displays its message); never type an `SKF_STACK_RESULT_JSON` line
37
+ - Warnings use a single accumulator: see `## Workflow state contract` below for shape and surfacing.
35
38
 
36
39
  ## Workflow state contract
37
40
 
38
- Every step that emits a warning appends a structured entry to a single in-memory list named `workflow_warnings[]` (the one accumulator for the whole workflow). Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Step 7 surfaces these in `evidence-report.md`; step 8 may add validation findings; step 9 §5 reads the accumulated list and renders the user-facing "Warnings" section.
41
+ Every step that emits a warning appends a structured entry to a single list named `workflow_warnings[]` (the one accumulator for the whole workflow). Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Appending one means recording it in the run sink at once, the `context` fields folded into the message. Write its `[{step}/{severity}] {code}: {message}` line to `{run_dir}/warning.txt` with a file write, never an `echo`: a message can quote backticks or `$( )`, which a shell runs. Then run:
39
42
 
40
- **Single-pass — no mid-run checkpoint.** State lives in memory until step 7 commits `provenance-map.json`/`evidence-report.md`; the workflow keeps no resumable checkpoint and On Activation does not probe for a prior run — the analysis is deterministic and cheap to redo, and the two gates are trivially re-confirmed. If interrupted before that commit, restart from step 1.
43
+ ```bash
44
+ uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/warning.txt")"
45
+ ```
46
+
47
+ The shell does not expand what the substitution prints, so the message reaches the sink as text.
48
+
49
+ The sink, `{run_dir}/warnings.jsonl`, is the list: step 7 §8b lists it in `evidence-report.md`, step 9 §1 renders it, and the result envelope carries it.
50
+
51
+ **Single-pass, no resume:** the run's state lives in `{run_dir}`, the run folder step 1 creates under `{project-root}/_bmad-output/.skf-run/` (the warnings and auto-decisions, step 3's import counts, step 4's extraction bundle and step 6's draft), and an interrupted run starts again from step 1.
41
52
 
42
53
  ## Stages
43
54
 
44
55
  | # | Step | File | Auto-proceed |
45
56
  |---|------|------|--------------|
46
- | 1 | Initialize & Mode Detection | references/init.md | No (confirm) |
47
- | 2 | Detect Manifests | references/detect-manifests.md | Yes |
57
+ | 1 | Initialize & Mode Detection | references/init.md | Conditional |
58
+ | 2 | Detect Manifests | references/detect-manifests.md | Conditional (no manifest found) |
48
59
  | 3 | Rank & Confirm Libraries | references/rank-and-confirm.md | No (confirm) |
49
60
  | 4 | Parallel Extract | references/parallel-extract.md | Yes |
50
61
  | 5 | Detect Integrations | references/detect-integrations.md | Yes |
@@ -56,35 +67,7 @@ Every step that emits a warning appends a structured entry to a single in-memory
56
67
 
57
68
  ## Invocation Contract
58
69
 
59
- | Aspect | Detail |
60
- |--------|--------|
61
- | **Inputs** | project_path [required], mode (code/compose) [auto-detected] |
62
- | **Gates** | step 3: Confirm Gate [C] | step 6: Review Gate [C] |
63
- | **Outputs** | SKILL.md (stack), context-snippet.md, metadata.json |
64
- | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true |
65
- | **Exit codes** | See "Exit Codes" below |
66
-
67
- ## Exit Codes
68
-
69
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
70
-
71
- | Code | Meaning | Raised by (halt_reason) |
72
- | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
73
- | 0 | success | step 10 (terminal handoff to shared health-check) |
74
- | 2 | input / precondition invalid | step 1 §0 `config.yaml` missing/malformed (`config-missing`); step 2 §2 headless with no manifests (`no-manifests`, S2); step 4 §3 all extractions failed (`all-extractions-failed`, B7); step 5 §2 feasibility-report `schemaVersion` mismatch (`schema-version-mismatch`) |
75
- | 3 | resolution-failure | step 1 §1 `forge-tier.yaml` missing (`forge-tier-missing`); step 2 §0 compose-mode skill-resolution corruption (manifest + symlink both fail); step 2 §0 compose-mode zero qualifying skills (S1/B4); step 4 §0 compose-cycle — all `resolution-failure` |
76
- | 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path — both `write-failure` |
77
- | 6 | user-cancelled | any interactive menu in step 3 / step 6 when the user selects `[X]` Cancel and exit (`user-cancelled`) |
78
-
79
- ## Result Contract (Headless)
80
-
81
- When `{headless_mode}` is true, step 9 emits a single-line JSON envelope on **stdout** before chaining to step 10, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
82
-
83
- ```
84
- SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","skill_name":"…","stack_libraries":["…"],"mode":"code|compose","exit_code":0,"halt_reason":null}
85
- ```
86
-
87
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode (`null` if the run halts before mode resolution). `halt_reason` is one of: `null` (success), `"config-missing"`, `"forge-tier-missing"`, `"no-manifests"`, `"all-extractions-failed"`, `"schema-version-mismatch"`, `"resolution-failure"`, `"write-failure"`, `"user-cancelled"`. `exit_code` matches the table above. Fields unknown at the halt point are `null` (`skill_name`) or `[]` (`stack_libraries`) — e.g. a `config-missing` halt precedes `project_name` resolution.
70
+ `references/invocation-contract.md` holds the inputs, gates and outputs, the exit code and `halt_reason` of every HARD HALT, and the `SKF_STACK_RESULT_JSON` result envelope.
88
71
 
89
72
  ## On Activation
90
73
 
@@ -100,30 +83,20 @@ SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","ski
100
83
  3. **Resolve workflow customization.** Run:
101
84
 
102
85
  ```bash
103
- python3 {project-root}/_bmad/scripts/resolve_customization.py \
104
- --skill {skill-root} --key workflow
86
+ uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow
105
87
  ```
106
88
 
107
- The script merges the three customization layers per `bmad-customize`'s structural merge rules (scalars override, arrays append):
108
-
109
- - `{skill-root}/customize.toml` — bundled defaults
110
- - `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
111
- - `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
112
-
113
- If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly — the bundled defaults are an empty string for each path scalar.
89
+ It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-create-stack-skill.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. In that case keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): `references/init.md` records it as the run's first warning once its pre-flight has created `{run_dir}`.
114
90
 
115
- Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the five scalars, if the merged value is empty or absent, use the bundled default:
91
+ Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each of the two scalars, if the merged value is empty or absent, use the bundled default:
116
92
 
117
- - `{stackSkillTemplatePath}` ← `workflow.stack_skill_template_path` if non-empty, else `assets/stack-skill-template.md`
93
+ - `{stackSkillTemplatePath}` ← `workflow.stack_skill_template_path` if non-empty, else `assets/stack-skill-template.md` (the SKILL.md, snippet and reference-file structures; `assets/metadata-contract.md` holds the `metadata.json` contract, which no override replaces)
118
94
  - `{integrationPatternsPath}` ← `workflow.integration_patterns_path` if non-empty, else `references/integration-patterns.md`
119
- - `{manifestPatternsPath}` ← `workflow.manifest_patterns_path` if non-empty, else `references/manifest-patterns.md`
120
- - `{composeModeRulesPath}` ← `workflow.compose_mode_rules_path` if non-empty, else `references/compose-mode-rules.md`
121
- - `{provenanceMapSchemaPath}` ← `workflow.provenance_map_schema_path` if non-empty, else `assets/provenance-map-schema.md`
122
95
 
123
- Also resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — `references/report.md` §6c skips the hook invocation entirely).
96
+ Also resolve `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op: `references/report.md` §2c then skips the hook).
124
97
 
125
- Stash all five paths plus `{onCompleteCommand}` as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` / `{integrationPatternsPath}` / `{manifestPatternsPath}` / `{composeModeRulesPath}` / `{provenanceMapSchemaPath}` directly; empty-string overrides fall through to the bundled default.
98
+ Stash both paths plus `{onCompleteCommand}` as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` and `{integrationPatternsPath}` directly; empty-string overrides fall through to the bundled default.
126
99
 
127
- Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents), then run `workflow.activation_steps_append` after.
100
+ Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents: the bundled default loads every `project-context.md` under `{project-root}`; an entry prefixed `!` drops each earlier entry it names and loads nothing itself, so an override's `"!file:{project-root}/**/project-context.md"` turns that default off), then run `workflow.activation_steps_append` after.
128
101
 
129
102
  4. Load, read the full file, and then execute `references/init.md` to begin the workflow.