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,17 +1,41 @@
1
1
  ---
2
2
  nextStepFile: '../enrich.md'
3
- # Resolve `{atomicWriteHelper}` by probing `{atomicWriteProbeOrder}` in order
3
+ forgeTierConfig: '{sidecar_path}/forge-tier.yaml'
4
+ # Resolve `{forgeTierRwHelper}` by probing `{forgeTierRwProbeOrder}` in order
4
5
  # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
- # path wins. HALT if neither resolves.
6
- atomicWriteProbeOrder:
7
- - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
8
- - '{project-root}/src/shared/scripts/skf-atomic-write.py'
6
+ # path wins. §5b changes the registry only through it. If neither resolves,
7
+ # §5b skips the registry change with a warning: a docs fetch never halts the
8
+ # workflow.
9
+ forgeTierRwProbeOrder:
10
+ - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
11
+ - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
9
12
  # Resolve `{deriveAssemblyShapeHelper}` the same way — used in §1 to decide
10
13
  # whether this is a whole-language reference (registry-corpora prose retained
11
14
  # as a Language Guide) or a standard skill (unchanged behaviour).
12
15
  deriveAssemblyShapeProbeOrder:
13
16
  - '{project-root}/_bmad/skf/shared/scripts/skf-derive-assembly-shape.py'
14
17
  - '{project-root}/src/shared/scripts/skf-derive-assembly-shape.py'
18
+ # If neither path exists, §3 looks for no subpages.
19
+ detectDocsProbeOrder:
20
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
21
+ - '{project-root}/src/shared/scripts/skf-detect-docs.py'
22
+ # §5 merges the T3 items into the extraction inventory and counts it through
23
+ # it; step 3 §5 already halted when neither path exists.
24
+ extractionInventoryProbeOrder:
25
+ - '{project-root}/_bmad/skf/shared/scripts/skf-extraction-inventory.py'
26
+ - '{project-root}/src/shared/scripts/skf-extraction-inventory.py'
27
+ # §4a writes the Language Guide's index beside the inventory through it; if
28
+ # neither path exists, step 5 has no guide and says so.
29
+ atomicWriteProbeOrder:
30
+ - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
31
+ - '{project-root}/src/shared/scripts/skf-atomic-write.py'
32
+ # HARD HALT helpers (Rules).
33
+ sourceTreeProbeOrder:
34
+ - '{project-root}/_bmad/skf/shared/scripts/skf-source-tree.py'
35
+ - '{project-root}/src/shared/scripts/skf-source-tree.py'
36
+ emitEnvelopeProbeOrder:
37
+ - '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
38
+ - '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
15
39
  ---
16
40
 
17
41
  <!-- Config: communicate in {communication_language}. -->
@@ -26,9 +50,10 @@ Fetch remote documentation from brief-specified URLs using whatever web fetching
26
50
 
27
51
  - No tier gate — runs at any tier when `doc_urls` are present in the brief
28
52
  - Tool-agnostic — use whatever web fetching capability is available
29
- - Do not halt the workflow if web fetching is unavailable or fails
53
+ - Do not halt the workflow if web fetching is unavailable or fails, with one exception: a `docs-only` brief left with nothing to compile halts at §5's zero-content check, before anything is staged. The user may still stop the run at §5's zero-export gate ([R])
30
54
  - Do not override existing T1, T1-low, or T2 extraction data with T3 content
31
- - Never delete the staging directory of a `docs-only` skill — the fetched pages are its only source corpus
55
+ - Never delete the staging directory of a `docs-only` skill, whose only source corpus is the fetched pages, or of a whole-language reference, whose Language Guide step 5 reads from them
56
+ - A HARD HALT, once step 3 §2b has bound `{source_tree}`, first runs `uv run {sourceTreeHelper} close --tree "{source_tree}"` from `{project-root}` (resolved from `{sourceTreeProbeOrder}`) and goes on whatever it prints, and emits through `{emitEnvelopeHelper}`, resolved from `{emitEnvelopeProbeOrder}` when it is not bound. After its envelope, under `--batch` it ends only this brief: return to `references/batch-mode.md` §3, even when the halt reads as the end of the run.
32
57
 
33
58
  ## MANDATORY SEQUENCE
34
59
 
@@ -62,67 +87,64 @@ Content fetched from external URLs is classified as **T3** (external, untrusted)
62
87
  **Discover available web fetching capability.** Try tools in any order — use whatever is accessible in the current environment (e.g., Firecrawl scrape, WebFetch, web-reader, MCP fetch, curl, browser tools). If no web fetching capability can be found:
63
88
 
64
89
  - Log warning: "No web fetching capability available in this environment. Skipping documentation fetch."
65
- - Skip to section 7 (auto-proceed).
90
+ - Continue at §5's **Zero-content check**: nothing was fetched.
66
91
 
67
- **For each URL in `doc_urls`:**
92
+ Bind `{docs_staging}` ← `{project-root}/_bmad-output/{skill-name}-docs`, the folder that holds every page this step fetches. Just before the first page this run fetched is saved, create the folder or empty it (`mkdir -p "{docs_staging}" && rm -f "{docs_staging}"/*.md`), so a corpus kept from an earlier docs-only run (§5c) is replaced by this run's pages, never mixed with them, and stays as it was when every fetch fails.
93
+
94
+ **For each URL in `doc_urls`** (the `{n}`th entry, counting from 1):
68
95
 
69
96
  - Fetch the content at `{url}` as clean markdown using the discovered web tool.
70
- - **If fetch succeeds:** Store the markdown content with the URL as provenance source.
97
+ - **If fetch succeeds:** Store the markdown content with the URL as provenance source, and save it as `{docs_staging}/page-{n}.md`.
71
98
  - **If fetch fails:** Log warning: "Failed to fetch {url}: {reason}. Skipping." Continue with remaining URLs.
72
99
 
73
100
  **Subpage discovery (root URL detection):**
74
101
 
75
- After fetching a URL, apply the following heuristic to detect documentation root pages that contain no useful API content. This is common with modern documentation sites (Mintlify, Docusaurus, ReadTheDocs, GitBook) that render API content on subpages.
76
-
77
- **Root page detection — apply only when the URL path ends in `/`, `/index`, `/index.html`, has no path component (bare domain), or has 1 path segment (e.g., `/docs`). For deeper URL paths (2+ segments like `/api/reference`), skip this heuristic and keep the content as-is.**
102
+ A documentation root page often holds no API content of its own: Mintlify, Docusaurus, ReadTheDocs and GitBook sites render it on subpages. Resolve `{detectDocsHelper}` from `{detectDocsProbeOrder}` (first existing path wins) and measure each saved page, from `{project-root}`:
78
103
 
79
- Subpage discovery is triggered if **either** of the following independent triggers fires:
80
-
81
- **Trigger 1 — Content-based (both conditions must be true):**
82
-
83
- 1. **Zero API content indicators:** The fetched markdown contains none of: fenced code blocks (`` ``` ``), parameter tables (`|---|`), or function signature patterns (`def `, `function `, `fn `, `func `, `export `).
84
- 2. **High link density:** More than 70% of non-empty lines are markdown links (matching `[text](url)` with no other substantive content on the line).
85
-
86
- **Trigger 2 — URL-based (independent of content analysis):**
87
-
88
- The URL matches the path criteria above (ends in `/`, bare domain, or 1 segment) AND the fetched content is under **2000 words**. Short content on root-like URLs almost certainly indicates a navigation hub or landing page, even if it contains introductory code examples that would prevent Trigger 1 from firing. This handles modern doc sites (Mintlify, Docusaurus, GitBook) that include hero sections with code snippets on their root pages.
104
+ ```bash
105
+ uv run {detectDocsHelper} page-metrics --url "{url}" "{docs_staging}/page-{n}.md"
106
+ ```
89
107
 
90
- If neither trigger fires, keep the page content as-is and do not trigger subpage discovery.
108
+ It prints `discover_subpages`, true when the page is a documentation root with little API content of its own (`uv run {detectDocsHelper} page-metrics --help` gives the test and the counts it prints with it). When it is false, the command exits non-zero, or no candidate resolves, keep the page as it is and look for no subpages.
91
109
 
92
- **If a root URL with minimal content is detected:**
110
+ **If `discover_subpages` is true:**
93
111
 
94
112
  1. **Attempt sitemap/map discovery:** Use whatever discovery tool is available:
95
113
  - Firecrawl: `firecrawl_map({url})` to discover all subpages
96
- - Manual: try fetching `{url}/sitemap.xml` and parsing URLs from it
114
+ - Manual: fetch `{url}/sitemap.xml`
97
115
  - Crawl: if a crawl tool is available, use it with depth=1 on the root URL
98
116
  - If no discovery tool is available, keep the root page content as-is and continue
99
117
 
100
- 2. **Filter discovered URLs by relevance and origin:** Restrict candidates to the same **registrable domain** as the root URL — strip the URL down to its eTLD+1 (e.g., for root `https://docs.example.com/intro`, accept any subdomain of `example.com` such as `api.example.com` or `docs.example.com`, but reject `example.org` or `cdn.partner.io`). Cross-origin links must be discarded before any fetch. The same-registrable-domain rule prevents Mintlify/Docusaurus link clouds from pulling in tracking pixels, doc-site CDNs, or third-party embeds as if they were canonical docs. From the surviving same-domain candidates, select the most relevant pages by searching for API-related terms in the URL path or title (e.g., `api`, `reference`, `quickstart`, `setup`, `config`, `getting-started`, `guide`, `sdk`, `methods`, `functions`). Exclude pages that are clearly non-API content (e.g., `blog`, `changelog`, `pricing`, `about`, `careers`).
118
+ 2. **Keep the subpages of the root's own site:** pass what discovery found to the helper on stdin, from `{project-root}`: pipe the sitemap in as below, or feed the map or crawl result (its JSON, or one URL per line) to the same command through a heredoc:
119
+
120
+ ```bash
121
+ curl -fsSL --max-time 30 "{url}/sitemap.xml" | uv run {detectDocsHelper} filter-urls --root "{url}" -
122
+ ```
123
+
124
+ It lists in `kept[]` the URLs on the root page's own site that may hold API content, each with the API words its path or title holds (`terms`), and in `sitemaps[]` the sitemaps a sitemap index names: filter each of them the same way (`uv run {detectDocsHelper} filter-urls --help` gives the rules). When it exits non-zero, leave that input out. Never fetch a URL the helper did not keep. From `kept[]`, choose the subpages most relevant to the skill's API: `terms` points at them.
101
125
 
102
126
  3. **Fetch top subpages (in parallel):** Fetch up to **10** of the most relevant subpages **concurrently** — subpage fetches are independent and network-bound, so wall-clock benefits substantially from parallel execution. Bound concurrency to **4 in flight** at a time to stay polite to documentation hosts (Mintlify/Docusaurus typically allow more, but conservatism here protects against unrecognized rate limits).
103
127
 
104
128
  The parallel pattern depends on the fetch tool:
105
129
 
106
130
  - **LLM-driven tools** (Firecrawl `firecrawl_scrape`, `WebFetch`, MCP fetch, browser tools): issue up to 4 tool calls **in a single message**. The agent runtime executes parallel tool calls concurrently; collect results from the batch before issuing the next set of up to 4. Repeat until all up-to-10 subpages have been attempted or rate limiting halts the batch.
107
- - **Bash-driven tools** (`curl`, `wget`): use `xargs -P 4 -n 1` to fan out from a newline-separated subpage list. Example:
131
+ - **Bash-driven tools** (`curl`, `wget`): use `xargs -P 4` to fan out from a newline-separated subpage list, numbering the subpages as it goes. Example, for the `{n}`th `doc_urls` entry:
108
132
 
109
133
  ```bash
110
- printf '%s\n' "${subpages[@]}" | xargs -P 4 -n 1 -I {} bash -c '
111
- url="{}"
112
- safe=$(echo -n "$url" | sha256sum | cut -c1-12)
113
- curl -sSL --max-time 30 "$url" > "{staging}/subpage-$safe.md" \
114
- || echo "fetch failed: $url" > "{staging}/subpage-$safe.md"
134
+ printf '%s\n' "${subpages[@]}" | awk '{ print NR, $0 }' | xargs -P 4 -L 1 sh -c '
135
+ f="{docs_staging}/subpage-{n}-$0.md"
136
+ curl -fsSL --max-time 30 "$1" -o "$f" || { rm -f "$f"; echo "fetch failed: $1" >&2; }
115
137
  '
116
138
  ```
117
139
 
118
140
  For each subpage (regardless of tool):
119
- - Use the same web fetching tool as the root URL
141
+ - Use the same web fetching tool as the root URL, and save the page in `{docs_staging}` as `subpage-{n}-{k}.md`, `{k}` counting this root's subpages from 1, only when its fetch succeeded: a failed fetch leaves no file, as in the snippet
120
142
  - Store with the subpage URL as provenance: `[EXT:{subpage-url}]`
121
143
  - If a subpage fetch fails, skip it and continue with the rest of the batch — do not halt the whole stage
122
144
 
123
145
  4. **Rate limiting:** If rate limiting (HTTP 429) is encountered during subpage fetching, stop discovery for this root URL. Keep results collected so far. Log: "Subpage discovery stopped due to rate limiting." For the parallel-tool-call pattern, drop any not-yet-issued tool calls from subsequent batches; for the `xargs` pattern, interrupt the pipeline (set `--max-procs 0` is **not** a graceful stop — the simplest stop is to kill the xargs PID and let in-flight writers complete naturally).
124
146
 
125
- **If ALL URLs fail (including any subpage fetches):** Log warning: "No documentation could be fetched. Proceeding without T3 content." Skip to section 7 (auto-proceed).
147
+ **If ALL URLs fail (including any subpage fetches):** Log warning: "No documentation could be fetched." `{docs_staging}` holds no page of this run: continue at §5's **Zero-content check**, which halts a `docs-only` brief and lets a source brief go on without T3 content.
126
148
 
127
149
  ### 4. Extract API Information from Fetched Content
128
150
 
@@ -137,60 +159,85 @@ Parse the successfully fetched markdown for:
137
159
 
138
160
  **No hallucination:** If information cannot be found in the fetched content, exclude it. Do not infer or fabricate API details.
139
161
 
140
- **Whole-language references — retain prose, do not shred (`whole_language_reference: true`):** For a whole-language reference the registry-sourced corpora (the guide/Book, the standard/library docs) ARE the product, not the compiler's internal exports. Reducing that prose to per-export signature items and then discarding it under the §5 "T3 never overrides T1" rule (the compiler's AST already owns names like `Vec`, `Option`, `HashMap`) would gut exactly the content the skill exists to teach. So for these briefs, skip §4a below for the registry corpora.
162
+ **Whole-language references (`whole_language_reference: true`): retain the prose, do not shred it.** For a whole-language reference the registry-sourced corpora (the guide/Book, the standard/library docs) ARE the product, not the compiler's internal exports. So for these briefs, extract no per-export items from the registry corpora (`source: language-registry`): §4a below keeps their pages as the Language Guide.
141
163
 
142
164
  ### 4a. Retain the Language Guide (whole-language references only)
143
165
 
144
166
  **Skip this section entirely unless `whole_language_reference: true`.** When it is true, for each `doc_urls` entry whose `source` is `language-registry`:
145
167
 
146
- - Do not reduce its fetched markdown to per-export items. Instead retain the cleaned prose as a Language-Guide entry `{url, label, prose}`, where `prose` is the substantive body (narrative, idioms, usage examples, conceptual reference) lightly trimmed of navigation/boilerplate, each block cited `[EXT:{url}]`.
147
- - Collect these into a `language_guide[]` context artifact, in `doc_urls` order.
168
+ - Do not reduce its fetched markdown to per-export items. Its pages stay in `{docs_staging}` as §3 saved them (`page-{n}.md` and its `subpage-{n}-{k}.md` files), and §5c keeps the folder: step 5 reads the prose from them and retains it as the skill's Language Guide, each block cited `[EXT:{url}]`.
169
+ - List the entry in a `language_guide[]` index, in `doc_urls` order, as `{url, label, pages}`: `pages` names the files of `{docs_staging}` that hold its fetched pages, and is empty when the fetch failed (warn: step 5 surfaces the gap rather than emitting a thin guide silently).
170
+
171
+ Write the index beside the extraction inventory, so step 5 finds the pages after a context compaction. Bind `{language_guide_json}` ← `{project-root}/_bmad-output/.skf-stage/{skill-name}.language-guide.json`, resolve `{atomicWriteHelper}` ← first existing path in `{atomicWriteProbeOrder}` and, from `{project-root}`, run:
148
172
 
149
- This artifact is a **distinct** carrier — it is not merged into the extraction inventory and is not subject to the §5 conflict rule, so the canonical prose survives intact into step 5 (compile), which foregrounds it as the skill's Language Guide. Non-registry docs (README-detected, homepage, Pages, docs-folder) still flow through §4's normal per-export extraction and the §5 merge unchanged.
173
+ ```bash
174
+ uv run {atomicWriteHelper} write --target "{language_guide_json}" <<'SKF_LANGUAGE_GUIDE'
175
+ {"docs_folder": "{docs_staging}", "language_guide": [{"url": "...", "label": "...", "pages": ["page-1.md"]}]}
176
+ SKF_LANGUAGE_GUIDE
177
+ ```
150
178
 
151
- **If a registry corpus could not be fetched** (network failure), record it in `language_guide[]` as `{url, label, prose: null}` and warn — step 5 surfaces the gap rather than emitting a thin guide silently.
179
+ When it fails, or no path resolves, warn "The Language Guide was not saved: {its message}": step 5 then assembles without it. The guide is a **distinct** carrier: it is not merged into the extraction inventory and is not subject to the §5 conflict rule, so the canonical prose survives intact into step 5 (compile), which foregrounds it as the skill's Language Guide. Non-registry docs (README-detected, homepage, Pages, docs-folder) still flow through §4's normal per-export extraction and the §5 merge unchanged.
152
180
 
153
181
  ### 5. Build Doc-Fetch Inventory
154
182
 
155
183
  **Mode determines merge behavior:**
156
184
 
157
- - **`source_type: "docs-only"`** — The doc-fetch inventory IS the extraction inventory. It replaces the empty inventory from step 3, since there was no source code to extract from.
158
- - **`source_type: "source"` (supplemental mode)** — Merge T3 items into the existing extraction inventory from step 3.
185
+ - **`source_type: "docs-only"`**: the doc-fetch inventory IS the extraction inventory. Its T3 items fill the empty inventory step 3 wrote, since there was no source code to extract from.
186
+ - **`source_type: "source"` (supplemental mode)**: the T3 items join the exports step 3 extracted.
187
+
188
+ Either way, merge them into `{extraction_inventory}`, the file step 3 §5 wrote, one object per item with its `[EXT:{url}]` citation and, when the item documents an export, its `export_name`. Resolve `{extractionInventoryHelper}` ← first existing path in `{extractionInventoryProbeOrder}` and, from `{project-root}`, run:
189
+
190
+ ```bash
191
+ uv run {extractionInventoryHelper} add --inventory "{extraction_inventory}" --field t3_items <<'SKF_T3'
192
+ [{"export_name": "...", "kind": "signature|type|config|example", "content": "...", "citation": "[EXT:{url}]"}]
193
+ SKF_T3
194
+ ```
159
195
 
160
- **Conflict rule:** T3 items never override existing T1, T1-low, or T2 items for the same export. When an export already has a higher-confidence entry, the T3 item is discarded — T3 has the lowest priority.
196
+ **Conflict rule:** T3 items never override existing T1, T1-low, or T2 items for the same export. The helper applies it: an item whose `export_name` names an export the inventory already holds is left out and listed in `dropped`. When it exits non-zero, fix the JSON and run it once more.
161
197
 
162
- **Language-Guide carve-out:** the `language_guide[]` artifact from §4a (whole-language references) is not part of the export inventory and is therefore not subject to this conflict rule — it carries no export key, so it cannot collide with a T1 compiler export and can never be pruned. It is passed separately into step 5, which renders it as the foregrounded Language Guide section. Only the per-export T3 items participate in the T1/T2/T3 merge.
198
+ **Language-Guide carve-out:** the `language_guide[]` from §4a (whole-language references) is not part of the export inventory and is therefore not subject to this conflict rule: it carries no export key, so it cannot collide with a T1 compiler export and can never be pruned. Step 5 reads it through `{language_guide_json}` and renders it as the foregrounded Language Guide section. Only the per-export T3 items participate in the T1/T2/T3 merge.
163
199
 
164
- **Edge case — T1-zero supplemental mode:** If T1 extraction produced zero results and `doc_urls` are present in supplemental mode, T3 items should be used as the primary inventory since no T1 data exists to conflict with.
200
+ **Edge case: T1-zero supplemental mode.** If T1 extraction produced zero results and `doc_urls` are present in supplemental mode, compile uses the T3 items as the primary inventory, since no T1 data exists to conflict with.
165
201
 
166
202
  **Aggregate totals for reporting:**
167
203
  - URLs fetched successfully vs. total
168
204
  - URLs that failed
169
205
  - T3 items extracted
170
206
 
207
+ **Zero-content check.** Run `uv run {extractionInventoryHelper} summary --inventory "{extraction_inventory}"` from `{project-root}` and read its `counts`:
208
+
209
+ - **`source_type: "docs-only"`, no URL or subpage fetched in this run, and `counts.items` is 0:** there is nothing to compile, and a docs-only skill has no other source. Stage and promote nothing: **HARD HALT** (exit code 3, `docs-unreachable`, phase `fetch-docs`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "**No documentation could be fetched for `{skill-name}`, so there is nothing to compile.** Failed: {each `doc_urls` URL with its reason}. Check the network and that this environment has a web fetch tool, or fix `doc_urls` in the brief, then re-run create-skill."
210
+ - **`source_type: "source"`, `counts.exports` is 0 (or equals `counts.not_public`) and every `doc_urls` fetch failed:** step 3 §6's zero-export check let this brief through because it declares `doc_urls`. Apply that check here: show its warning and offer **[C] Continue anyway** or **[R] Refine the brief**: stop here. **GATE [default: C]**: under `{headless_mode}`, log and record its `zero-exports` decision as step 3 §6 says, and continue. On [R], **HARD HALT** (exit code 6, `halted-for-brief-refinement`, phase `fetch-docs`; stage `{run_dir}/halt.json` per the Workflow Rules and run `uv run {emitEnvelopeHelper} emit-halt --workflow skf-create-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"`): "**Halting create-skill:** no public export was extracted (or, for a `public-api` brief, every export found is off the public surface: the `Off the public surface` warning names them) and no `doc_urls` page could be fetched. Fix `scope.include`, `target_version` or `doc_urls` in the brief (or re-run `skf-brief-skill`), then re-run `skf-create-skill`."
211
+ - **Otherwise:** continue. When no page of this run was saved (every fetch failed, or no fetch tool), skip §5b and §5c, which have nothing to index or remove, and go on to §6: `{docs_staging}` stays as it was.
212
+
171
213
  ### 5b. Index into QMD (Deep Tier Only)
172
214
 
173
215
  **If tier is not Deep:** Skip this section silently.
174
216
 
175
217
  **If tier is Deep and at least one URL was fetched successfully:**
176
218
 
177
- 1. Write fetched markdown files to a staging directory: `_bmad-output/{skill-name}-docs/` — clear any previous contents first, so a corpus retained from an earlier docs-only run (step 5 below) does not mix with this run's pages.
178
- 2. Index into QMD with atomic replace + rollback: if a `{skill-name}-docs` collection already exists, run `qmd collection remove {skill-name}-docs` first, then `qmd collection add {project-root}/_bmad-output/{skill-name}-docs/ --name {skill-name}-docs --mask "*.md"`. **If `qmd collection add` fails after a successful `remove`:** remove any matching `{skill-name}-docs` entry from `forge-tier.yaml` → `qmd_collections[]` to keep the registry consistent with QMD's actual state, warn in evidence-report, and skip the embed — docs enrichment degrades gracefully.
179
- 3. Generate embeddings scoped to this collection (only if step 2 `add` succeeded): `qmd embed --collection {skill-name}-docs` (required for semantic `type:'vec'` and HyDE `type:'hyde'` sub-queries within the QMD `query` tool). If the installed `qmd` CLI does not accept `--collection`, gate the embed behind a freshness check: skip re-embedding if the existing `{skill-name}-docs` registry entry is within 24 hours, and log the skip in the evidence report to prevent unbounded batch-mode re-embedding.
180
- 4. Register in forge-tier.yaml `qmd_collections` array — **acquire an exclusive `flock` on `{sidecar_path}/forge-tier.yaml.lock` for the read-modify-write** (see the locking pattern documented in step 3b §4). Write via `python3 {atomicWriteHelper} write --target {sidecar_path}/forge-tier.yaml`. If `flock` is unavailable, fall back to read-CAS-by-mtime (capture `st_mtime` before, re-check after; refuse to clobber if a concurrent run wrote in between).
181
-
182
- ```yaml
183
- - name: "{skill-name}-docs"
184
- type: "docs"
185
- source_workflow: "create-skill"
186
- skill_name: "{skill-name}"
187
- created_at: "{current ISO date}"
188
- ```
219
+ 1. The fetched pages are already in `{docs_staging}` (section 3), which this section indexes.
220
+ 2. Index into QMD with atomic replace + rollback: if a `{skill-name}-docs` collection already exists, run `qmd collection remove {skill-name}-docs` first, then `qmd collection add {project-root}/_bmad-output/{skill-name}-docs/ --name {skill-name}-docs --mask "*.md"`. Resolve `{forgeTierRwHelper}` from `{forgeTierRwProbeOrder}`: every registry change of this section goes through it, and it holds `{sidecar_path}/forge-tier.yaml.lock` for its one read-modify-write, so no step takes a lock of its own. **If `qmd collection add` fails after a successful `remove`:** remove the registry entry too, so the registry matches QMD's actual state, warn in evidence-report, and skip steps 3 and 4, since docs enrichment degrades gracefully:
221
+
222
+ ```bash
223
+ uv run {forgeTierRwHelper} remove-qmd-collection --target "{forgeTierConfig}" --name {skill-name}-docs
224
+ ```
189
225
 
190
- 5. Clean up the staging directory after indexing — **only when `source_type` is `"source"`**: `rm -rf {project-root}/_bmad-output/{skill-name}-docs/`. Note that this directory is the source path of the `{skill-name}-docs` collection registered in step 4; removing it is accepted for supplemental docs, whose T3 items already live in the extraction inventory. **When `source_type` is `"docs-only"`, keep the directory.** The fetched pages are the skill's only source corpus — there is no code tree — so deleting them would leave the just-registered collection with nothing to refresh from and nothing to verify citations against. Record the retained path in the evidence report.
226
+ 3. Generate embeddings scoped to this collection (only if step 2 `add` succeeded): `qmd embed --collection {skill-name}-docs` (required for semantic `type:'vec'` and HyDE `type:'hyde'` sub-queries within the QMD `query` tool). If the installed `qmd` CLI does not accept `--collection`, gate the embed behind a freshness check: read the registry with `uv run {forgeTierRwHelper} read --target "{forgeTierConfig}"`, skip re-embedding when the `{skill-name}-docs` entry in its `data.qmd_collections` was created within the last 24 hours, and log the skip in the evidence report to prevent unbounded batch-mode re-embedding.
227
+ 4. Register the collection in the `qmd_collections` array (only if step 2 `add` succeeded). The helper replaces the entry with the same `name` or appends it:
228
+
229
+ ```bash
230
+ uv run {forgeTierRwHelper} register-qmd-collection --target "{forgeTierConfig}" <<'SKF_REGISTRY_ENTRY'
231
+ {"name": "{skill-name}-docs", "type": "docs", "source_workflow": "create-skill", "skill_name": "{skill-name}", "created_at": "{current ISO date}"}
232
+ SKF_REGISTRY_ENTRY
233
+ ```
191
234
 
192
235
  **If QMD indexing fails:** Warn: "QMD indexing of fetched docs failed. T3 items are still in the extraction inventory — enrichment will proceed without QMD-indexed docs." Continue.
193
236
 
237
+ ### 5c. Keep or Remove the Fetched Pages
238
+
239
+ Once the pages are extracted, and at Deep tier indexed by §5b, remove `{docs_staging}` **only when `source_type` is `"source"` and the brief is not a whole-language reference** (`whole_language_reference` false): `rm -rf {project-root}/_bmad-output/{skill-name}-docs/`. A whole-language reference keeps the folder, since its Language Guide index (§4a) points step 5 at the pages in it. At Deep tier the folder is the source path of the `{skill-name}-docs` collection §5b registered; removing it is accepted for supplemental docs, whose T3 items already live in the extraction inventory. **When `source_type` is `"docs-only"`, keep the folder at every tier:** the fetched pages are the skill's only source corpus, since there is no code tree, so deleting them would leave nothing to verify citations against and, at Deep tier, the just-registered collection with nothing to refresh from. Record the kept path in the evidence report.
240
+
194
241
  ### 6. Report
195
242
 
196
243
  Display:
@@ -200,7 +247,7 @@ Display:
200
247
  **T3 items extracted:** {count}
201
248
  **Confidence:** All doc-fetched items are T3 — `[EXT:{url}]` citations applied.
202
249
  {If docs-only mode: '**Mode:** Docs-only — all skill content is T3. source_authority: community'}
203
- {If docs-only mode AND tier is Deep: '**Docs corpus retained:** `_bmad-output/{skill-name}-docs/`{if the `{skill-name}-docs` collection was registered in §5b: ' — source path of QMD collection `{skill-name}-docs`'}'}
250
+ {If docs-only mode or a whole-language reference: '**Docs corpus kept:** `{docs_staging}`{if the `{skill-name}-docs` collection was registered in §5b: ', the source path of QMD collection `{skill-name}-docs`'}'}
204
251
 
205
252
  Proceeding to enrichment..."
206
253
 
@@ -1,11 +1,19 @@
1
1
  ---
2
2
  nextStepFile: 'fetch-docs.md'
3
- # Resolve `{atomicWriteHelper}` by probing `{atomicWriteProbeOrder}` in order
3
+ forgeTierConfig: '{sidecar_path}/forge-tier.yaml'
4
+ # Resolve `{forgeTierRwHelper}` by probing `{forgeTierRwProbeOrder}` in order
4
5
  # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
- # path wins. HALT if neither resolves.
6
- atomicWriteProbeOrder:
7
- - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
8
- - '{project-root}/src/shared/scripts/skf-atomic-write.py'
6
+ # path wins. §2 reads the registry through it and §4 changes the registry only
7
+ # through it. If neither resolves, §2 finds no cached entry and §4 skips the
8
+ # registry change with a warning: temporal enrichment never halts the workflow.
9
+ forgeTierRwProbeOrder:
10
+ - '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
11
+ - '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
12
+ # If neither path exists, §1 skips the step: temporal enrichment never
13
+ # halts the workflow.
14
+ fetchTemporalProbeOrder:
15
+ - '{project-root}/_bmad/skf/shared/scripts/skf-fetch-temporal.py'
16
+ - '{project-root}/src/shared/scripts/skf-fetch-temporal.py'
9
17
  ---
10
18
 
11
19
  <!-- Config: communicate in {communication_language}. -->
@@ -14,47 +22,44 @@ atomicWriteProbeOrder:
14
22
 
15
23
  ## STEP GOAL:
16
24
 
17
- To fetch temporal context (issues, PRs, changelogs, release notes) from the source repository and index it into a QMD collection for Deep tier enrichment. This ensures step 4 has historical data to search when annotating extracted functions with T2 provenance.
25
+ To fetch temporal context (issues, PRs, changelogs, release notes) from the source repository, keep the fetched files as the skill's temporal feeder, and index them into a QMD collection for Deep tier enrichment. This ensures step 4 has historical data to search when annotating extracted functions with T2 provenance, and that step 5c's doc-rot scan can read the same files.
18
26
 
19
27
  ## Rules
20
28
 
21
29
  - Deep tier only — Quick, Forge, and Forge+ tiers skip this step entirely and silently
22
30
  - GitHub repositories only — other source types degrade gracefully
23
31
  - Do not halt the workflow if fetching or indexing fails
24
- - Do not modify extraction data from step 3 — this step only creates QMD collections
32
+ - Do not modify the extraction inventory step 3 wrote: this step only reads its `top_exports`, and writes the temporal feeder folder, the fetch folder that replaces it, and the QMD collection indexed from it
25
33
 
26
34
  ## MANDATORY SEQUENCE
27
35
 
28
36
  ### 1. Check Eligibility
29
37
 
30
- Evaluate the following conditions sequentially. **If ANY condition fails, skip silently to section 5 (auto-proceed) with no output:**
38
+ Set `{temporal_feeder}` to null first, so that in a `--batch` run one brief's feeder never carries into the next. Then evaluate the following conditions sequentially. **If ANY condition fails, leave `{temporal_feeder}` null and skip silently to section 5 (auto-proceed) with no output:**
31
39
 
32
40
  1. **Tier is Deep:** If tier is Quick, Forge, or Forge+, skip silently.
33
- 2. **Source is GitHub:** Verify `source_repo` is a GitHub URL (`https://github.com/...`) or `owner/repo` format. If the source is a local path, a non-GitHub URL, or any other format, attempt GitHub remote detection (section 1b) before skipping.
34
- 3. **`gh` CLI is available:** Run `timeout 10s gh auth status` to verify the CLI is installed and authenticated (the short timeout protects against a misconfigured network or hung auth helper blocking the workflow). If it fails or times out, skip silently.
41
+ 2. **The source is a GitHub repository `gh` can read:** resolve `{fetchTemporalHelper}` from `{fetchTemporalProbeOrder}` and, from `{project-root}`, run:
35
42
 
36
- All three conditions must pass to proceed to section 2.
43
+ ```bash
44
+ uv run {fetchTemporalHelper} repo --source-repo "{source_repo}"
45
+ ```
37
46
 
38
- ### 1b. GitHub Remote Detection for Local Sources
47
+ Skip silently when the command exits non-zero, when its `skip_reason` is not null, and when no candidate resolves. Otherwise bind `{temporal_repo}` ← its `repo`, and when its `via` is `origin`, log: "**Local source with GitHub remote detected:** {temporal_repo}. Fetching temporal context."
39
48
 
40
- **Only runs when condition 2 above fails because `source_repo` is a local path.**
49
+ Both conditions must pass to proceed to section 2. Then bind `{temporal_feeder}` ← `{forge_data_folder}/{skill-name}/.skf-temporal`: the folder where this step keeps what it fetched (sections 3 and 4). Step 5c's doc-rot scan reads the files in it, and a null `{temporal_feeder}` tells step 5c that no temporal feeder was expected.
41
50
 
42
- Local repositories that are clones of GitHub repos contain temporal context (issues, PRs, releases) accessible via `gh`. Detect this automatically:
51
+ ### 2. Check Cache (Skip If Fresh)
43
52
 
44
- 1. Check if the local path is a git repository: `git -C "{source_repo}" rev-parse --is-inside-work-tree`
45
- 2. If not a git repo: skip silently to section 5 (current behavior).
46
- 3. Extract the origin remote: `git -C "{source_repo}" remote get-url origin`
47
- 4. If the remote URL contains `github.com`:
48
- - Extract `owner/repo` from the remote URL (strip `.git` suffix, handle both HTTPS and SSH formats)
49
- - Log: "**Local source with GitHub remote detected:** {owner}/{repo} — fetching temporal context."
50
- - Use the extracted `owner/repo` for all `gh` API calls in sections 3-4. Continue to condition 3 (gh CLI check).
51
- 5. If no remote, or remote is not GitHub: skip silently to section 5 (current behavior).
53
+ Resolve `{forgeTierRwHelper}` from `{forgeTierRwProbeOrder}` and read the registry as JSON, from `{project-root}`:
52
54
 
53
- ### 2. Check Cache (Skip If Fresh)
55
+ ```bash
56
+ uv run {forgeTierRwHelper} read --target "{forgeTierConfig}"
57
+ ```
54
58
 
55
- Read `forge-tier.yaml` from the sidecar path.
59
+ Bind `{registry_collections}` ← its `data.qmd_collections`: an empty list when `exists` is false, the list is missing, the command fails or no candidate resolves. Section 4 reads it again for its embed check.
56
60
 
57
- - Look for a `qmd_collections` entry where `skill_name` matches the current brief AND `type` is `"temporal"`.
61
+ - Look for a `{registry_collections}` entry where `skill_name` matches the current brief AND `type` is `"temporal"`.
62
+ - If `{temporal_feeder}` holds no `.md` file, continue to section 3 even when that entry is fresh: a cache hit must leave step 5c files to read, and an earlier SKF release deleted the folder once it was indexed.
58
63
  - If found AND `created_at` is within the last **7 days** (rationale: temporal context — issues, PRs, changelogs — rarely changes meaningfully on shorter horizons; a 7-day window balances freshness against re-fetch cost and GitHub rate limits): the temporal collection is fresh. Display:
59
64
 
60
65
  "**Temporal context: cached.** Collection `{skill-name}-temporal` is fresh ({days} days old). Skipping re-fetch."
@@ -65,175 +70,57 @@ Skip to section 5 (auto-proceed).
65
70
 
66
71
  ### 3. Fetch Temporal Context
67
72
 
68
- Create a staging directory: `_bmad-output/{skill-name}-temporal/`
69
-
70
- Resolve the `owner` and `repo` from `source_repo` (e.g., `acme/toolkit` from `https://github.com/acme/toolkit`).
71
-
72
- Execute the four fetches below **in parallel** — they are independent and the network round-trips dominate wall-clock. Background each, then `wait` for the batch. **If any individual fetch fails, log a warning and continue with the others.** The 4-concurrent fan-out is well under GitHub's authenticated REST rate limit (5000/hr); no bounded-concurrency guard is needed for this set.
73
+ Fetch through the helper, from `{project-root}`. `--exports` names the extraction inventory step 3 §5 wrote, `{extraction_inventory}`: the helper reads its `top_exports` for the targeted issue searches, and an empty list (docs-only mode, or a source extraction with no public exports) runs none:
73
74
 
74
75
  ```bash
75
- mkdir -p {staging}
76
- # 1. Issues (last 100)
77
- ( gh issue list -R {owner}/{repo} --state all --limit 100 \
78
- --json number,title,state,labels,createdAt,closedAt,body \
79
- | jq -r '...' > {staging}/issues.md ) &
80
- # 2. Merged PRs (last 100)
81
- ( gh pr list -R {owner}/{repo} --state merged --limit 100 \
82
- --json number,title,mergedAt,labels,body \
83
- | jq -r '...' > {staging}/prs.md ) &
84
- # 3. Release tags only (the per-tag fetch loop runs sequentially below to
85
- # preserve append-immediately crash-resume semantics for releases.md)
86
- ( gh release list -R {owner}/{repo} --limit 10 \
87
- --json tagName,name,publishedAt > {staging}/.release-tags.json ) &
88
- # 4. Changelog (404 is silent skip — note `set +e` so the subshell doesn't
89
- # propagate `gh api`'s non-zero exit on missing file)
90
- ( set +e
91
- gh api repos/{owner}/{repo}/contents/CHANGELOG.md --jq '.content' \
92
- | base64 -d > {staging}/changelog.md 2>/dev/null
93
- [ -s {staging}/changelog.md ] || rm -f {staging}/changelog.md ) &
94
- wait
76
+ uv run {fetchTemporalHelper} fetch --repo "{temporal_repo}" --feeder "{temporal_feeder}" --exports "{extraction_inventory}"
95
77
  ```
96
78
 
97
- Per-call rationale:
98
-
99
- 1. **Issues (last 100):** 100 is `gh issue list`'s default max-per-page; one paginated call captures recent activity without extra round trips or rate-limit pressure. Output → `{staging}/issues.md` formatted as a markdown document with one section per issue.
100
-
101
- 2. **Merged PRs (last 100):** Same 100-per-page convention as issues — captures the most recent merges in one API call. Output → `{staging}/prs.md`.
102
-
103
- 3. **Release tags + per-release fetches (last 10):** Release notes accumulate slowly relative to issues/PRs; the most recent 10 tags cover roughly the last 6-18 months of changelog-relevant history for typical OSS projects, which is enough context for T2-past annotations without fanning out to dozens of `gh release view` calls.
104
-
105
- **Note:** `gh release list --json` does **not** support the `body` field. The parallel block above fetches tags only (Step 1). After `wait`, run **Step 2 sequentially** to preserve the append-immediately crash-resume contract:
106
-
107
- If `{staging}/.release-tags.json` is empty (no releases), skip Step 2 and omit the releases section entirely. Otherwise:
108
-
109
- ```bash
110
- # Sequential per-tag loop. Iterate the JSON tag array verbatim so a
111
- # crash mid-loop leaves a partial-but-well-formed releases.md on disk.
112
- echo "# Releases (partial if interrupted)" > {staging}/releases.md
113
- ERR_FILE="{staging}/.gh-release-err" # per-run stderr capture inside staging
114
- jq -r '.[].tagName' {staging}/.release-tags.json | while IFS= read -r tag; do
115
- if gh release view "$tag" -R {owner}/{repo} \
116
- --json tagName,name,publishedAt,body 2>"$ERR_FILE"; then
117
- jq -r '...' >> {staging}/releases.md # one ## {tag} block per release
118
- else
119
- echo "## $tag — fetch failed: $(cat "$ERR_FILE")" \
120
- >> {staging}/releases.md
121
- fi
122
- done
123
- rm -f "$ERR_FILE"
124
- ```
125
-
126
- Failed individual fetches get a one-line placeholder; the loop continues with remaining tags. If a rate limit (HTTP 429) is hit, stop the release loop, keep the partial `releases.md` file in place (do not delete it), and log: "Release fetch stopped at tag {N}/{total} due to rate limiting — partial releases.md retained."
127
-
128
- **Why sequential here when the rest is parallel:** the append-per-release pattern guarantees that a mid-loop abort (rate limit, network drop, user interrupt) leaves a partial but well-formed `releases.md` with every release fetched so far. Parallel writers appending to the same file would need file locking and per-writer ordering — the simpler sequential loop is robust for free, and 10 release fetches contribute only ~5-10s of the total wall-clock.
129
-
130
- 4. **Changelog:** `CHANGELOG.md` or `RELEASES.md` at the repository root. The parallel block above writes to `{staging}/changelog.md` only when the file exists; non-existence (404 from `gh api`) leaves no file behind.
79
+ It fetches into a fresh folder beside `{temporal_feeder}` and replaces the feeder with it only when the fetch returned something (`uv run {fetchTemporalHelper} --help` describes the files it writes and the swap).
131
80
 
132
- #### 3b. Targeted Function Searches (Uses Extraction Inventory)
81
+ Show each of its `warnings`. Then:
133
82
 
134
- After the generic fetches above, perform **targeted searches** using the top-level public API function names from `extraction_inventory.top_exports[]`. This produces high-signal results that generic list fetches miss.
135
-
136
- **Short-circuit on empty `top_exports`:** If `extraction_inventory.top_exports` is missing or `== []` (docs-only mode, or a source extraction that produced zero public exports), skip this sub-section entirely with a one-line log: "No exports in inventory — skipping targeted function searches." The generic fetches from §3 remain in place and continue to provide baseline temporal context.
137
-
138
- **Limit:** Search the top **10 function names** maximum to control API call volume and avoid `gh` rate limiting. (rationale: 10 targeted searches + generic fetches from §3 stays well under GitHub's unauthenticated search rate limit of 10 requests/minute and authenticated 30/minute; matches the `top_exports[]` size emitted by step 3 §5 so every tracked export gets one search.)
139
-
140
- For each function name in `top_exports[]` (up to 10), **sanitize first**: strip every character that is not in `[A-Za-z0-9_]` from `function_name` to produce `safe_name`. This prevents shell injection and `gh` query parser errors when an export name contains punctuation (e.g., `<T>`, `.method`, `::namespace`, quotes). If `safe_name` is empty after sanitization (the original was entirely punctuation — rare but possible for symbol exports), fall back to piping the original name through stdin via `--query-from-file -`-style indirection if your `gh` version supports it; otherwise skip that one entry with a log line — never substitute the unsanitized name back into the shell command.
141
-
142
- **Parallel fan-out (bounded concurrency = 5):** sanitized searches are independent and benefit from parallelism. Use `xargs -P 5` so at most 5 `gh search` calls are in flight at once — this stays well under GitHub's authenticated search rate limit (30/min) while saving ~5-8s of wall-clock on a top-10 export list compared to fully sequential.
143
-
144
- ```bash
145
- # 1. Sanitize and write one safe_name per line; skip empties.
146
- # Python is the canonical sanitizer because awk/sed regex semantics
147
- # vary across platforms.
148
- jq -r '.top_exports[]' {extraction_inventory.json} \
149
- | python3 -c 'import sys,re
150
- for line in sys.stdin:
151
- s = re.sub(r"[^A-Za-z0-9_]", "", line.strip())
152
- if s: print(s)' > {staging}/.safe-names.txt
153
-
154
- # 2. Fan out to gh search, 5 in flight. Each writer emits a self-contained
155
- # section to its own per-name file; we concatenate after the wait.
156
- # --limit 5: top-5 issues per function keeps signal-to-noise high and caps
157
- # total response size across 10 fan-outs at 50 issues.
158
- mkdir -p {staging}/targeted
159
- cat {staging}/.safe-names.txt | xargs -P 5 -I {} bash -c '
160
- gh search issues --repo {owner}/{repo} "{}" --limit 5 \
161
- --json number,title,state,body 2>/dev/null \
162
- | jq -r ". | \"## {}\\n\" + (...)" > {staging}/targeted/{}.md \
163
- || echo "## {} — fetch failed" > {staging}/targeted/{}.md
164
- '
165
-
166
- # 3. Concatenate in stable order (alpha by safe_name) into the single
167
- # aggregated file the rest of the workflow expects.
168
- sort {staging}/.safe-names.txt | while IFS= read -r name; do
169
- cat "{staging}/targeted/$name.md"
170
- done > {staging}/targeted-issues.md
171
- ```
172
-
173
- Aggregate all targeted search results into a single file: `{staging}/targeted-issues.md`. The per-name temp directory `{staging}/targeted/` can be removed after concat — it exists only to give each parallel writer an isolated output stream.
174
-
175
- **If `gh search` is unavailable** (older `gh` CLI versions): skip targeted searches silently. The generic fetches from section 3 still provide baseline temporal context.
176
-
177
- **If rate limiting occurs** (HTTP 429 or similar): stop targeted searches immediately, keep results collected so far. Log: "Targeted search stopped at function {N}/{total} due to rate limiting."
178
-
179
- **After all fetching,** verify at least one file was written to the staging directory. If the staging directory is empty (all fetches failed), log a warning and skip to section 5.
83
+ - **Exit 0** (`status: "replaced"`): `{temporal_feeder}` now holds this fetch, and `files` lists it. Continue to section 4.
84
+ - **Exit 3** (`status: "kept"`: no fetch returned anything): log a warning and skip to section 5. `{temporal_feeder}` stays bound and keeps what the last good fetch left in it: step 5c scans those files, or reports the missing feeder in the evidence report when there are none.
85
+ - **Exit 1 or 2:** as for exit 3, with the helper's error (its stderr) in the warning.
180
86
 
181
87
  ### 4. Index Into QMD & Register
182
88
 
183
- **Index the staging directory:**
89
+ **Index `{temporal_feeder}`:**
184
90
 
185
- If a `{skill-name}-temporal` collection already exists, remove and recreate for atomic replace. **Wrap the remove + add pair with rollback on `add` failure** — a `remove` that succeeds followed by an `add` that fails must not leave the registry claiming a collection that no longer exists in QMD:
91
+ If a `{skill-name}-temporal` collection already exists, remove and recreate for atomic replace. **Wrap the remove + add pair with rollback on `add` failure:** a `remove` that succeeds followed by an `add` that fails must not leave the registry claiming a collection that no longer exists in QMD. The registry changes below go through `{forgeTierRwHelper}` (resolved in section 2), which holds `{sidecar_path}/forge-tier.yaml.lock` for its one read-modify-write, so no step takes a lock of its own:
186
92
 
187
93
  ```bash
188
94
  qmd collection remove {skill-name}-temporal
189
- if ! qmd collection add {project-root}/_bmad-output/{skill-name}-temporal/ --name {skill-name}-temporal --mask "*.md"; then
190
- # add failed after remove succeeded — the collection is gone from QMD. Clean the registry too.
191
- # Remove any {skill-name}-temporal entry from forge-tier.yaml qmd_collections[].
192
- # Warn the user, do not fail the workflow (temporal enrichment degrades gracefully).
193
- echo "WARN: qmd add failed after remove — registry entry for {skill-name}-temporal removed to keep forge-tier.yaml consistent with QMD state."
194
- # [skip the embed step]
95
+ if ! qmd collection add "{temporal_feeder}" --name {skill-name}-temporal --mask "*.md"; then
96
+ # add failed after remove succeeded: the collection is gone from QMD, so its registry entry goes too.
97
+ uv run {forgeTierRwHelper} remove-qmd-collection --target "{forgeTierConfig}" --name {skill-name}-temporal
98
+ echo "WARN: qmd add failed after remove; registry entry for {skill-name}-temporal removed to keep forge-tier.yaml consistent with QMD state."
195
99
  else
196
100
  qmd embed --collection {skill-name}-temporal
197
101
  fi
198
102
  ```
199
103
 
200
- **Rollback rule:** if the `qmd collection add` step fails (non-zero exit, network error, parse error) after the prior `remove` succeeded, remove the canonical registry entry in `forge-tier.yaml` to match QMD's actual state. A dangling registry entry that points at a non-existent QMD collection poisons subsequent cache-hit checks in §2. Emit a warning in evidence-report and skip the embed — enrichment degrades to no-QMD for this run.
104
+ **Rollback rule:** if the `qmd collection add` step fails (non-zero exit, network error, parse error) after the prior `remove` succeeded, the `remove-qmd-collection` call above removes the registry entry, to match QMD's actual state. A dangling registry entry that points at a non-existent QMD collection poisons subsequent cache-hit checks in §2. Emit a warning in evidence-report and skip the embed and the registration below: enrichment degrades to no-QMD for this run.
201
105
 
202
- **Scope the embed:** Always pass `--collection {skill-name}-temporal` to `qmd embed`. An unscoped `qmd embed` re-embeds every collection in the QMD store, which can take minutes per run in batch mode and generates wasteful GPU/API cost. If the installed `qmd` CLI does not accept `--collection` (older upstream versions), gate the embed behind a per-skill check: if a previous `{skill-name}-temporal` entry already exists in `qmd_collections` and its `created_at` is within 24 hours, skip the embed entirely and warn "qmd embed skipped — upstream qmd lacks --collection scope; re-embedding all collections would be wasteful in batch mode". Log the skip in the evidence report.
106
+ **Scope the embed:** Always pass `--collection {skill-name}-temporal` to `qmd embed`. An unscoped `qmd embed` re-embeds every collection in the QMD store, which can take minutes per run in batch mode and generates wasteful GPU/API cost. If the installed `qmd` CLI does not accept `--collection` (older upstream versions), gate the embed behind a per-skill check: if the `{skill-name}-temporal` entry section 2 found in `{registry_collections}` has a `created_at` within 24 hours, skip the embed entirely and warn "qmd embed skipped: upstream qmd lacks --collection scope; re-embedding all collections would be wasteful in batch mode". Log the skip in the evidence report.
203
107
 
204
108
  **Note:** `qmd embed` generates vector embeddings required for semantic (`type:'vec'`) and HyDE (`type:'hyde'`) sub-queries inside the QMD `query` tool. Without embeddings, only BM25 (`type:'lex'`) keyword search works. Run `qmd embed` after every `qmd collection add`.
205
109
 
206
- **Update the registry** in `forge-tier.yaml` under a file lock to prevent concurrent batch runs from clobbering each other's entries:
207
-
208
- 1. Acquire an exclusive `flock` on `{sidecar_path}/forge-tier.yaml.lock` (create the lock file if absent). Use `flock -x {lockfile} -c "..."` or an equivalent `fcntl.flock(LOCK_EX)` guard.
209
- 2. Read the current `forge-tier.yaml`, capturing its `st_mtime` as `mtime_before`.
210
- 3. Perform the read-modify-write below.
211
- 4. Write via `python3 {atomicWriteHelper} write --target {sidecar_path}/forge-tier.yaml`.
212
- 5. Release the flock.
213
-
214
- **Fallback when `flock` is unavailable:** re-stat the file after the write; if the on-disk `st_mtime` is newer than `mtime_before` by more than this run's own write timestamp, halt with "forge-tier.yaml modified mid-update by another process — refusing to clobber. Re-run after the other run completes." This read-CAS-by-mtime is the belt-and-braces safety net for environments without `flock`.
215
-
216
- If an entry with `name: "{skill-name}-temporal"` already exists in `qmd_collections`, replace it. Otherwise, append:
217
-
218
- ```yaml
219
- - name: "{skill-name}-temporal"
220
- type: "temporal"
221
- source_workflow: "create-skill"
222
- skill_name: "{skill-name}"
223
- created_at: "{current ISO date}"
224
- ```
225
-
226
- **Clean up** the staging directory after successful indexing:
110
+ **Register the collection** only when `qmd collection add` succeeded. The helper replaces the entry with `name: "{skill-name}-temporal"` or appends it, and its lock keeps concurrent batch runs from losing each other's entries:
227
111
 
228
112
  ```bash
229
- rm -rf {project-root}/_bmad-output/{skill-name}-temporal/
113
+ uv run {forgeTierRwHelper} register-qmd-collection --target "{forgeTierConfig}" <<'SKF_REGISTRY_ENTRY'
114
+ {"name": "{skill-name}-temporal", "type": "temporal", "source_workflow": "create-skill", "skill_name": "{skill-name}", "created_at": "{current ISO date}"}
115
+ SKF_REGISTRY_ENTRY
230
116
  ```
231
117
 
118
+ **Keep `{temporal_feeder}`.** It is the source path of the `{skill-name}-temporal` collection and the temporal feeder step 5c's doc-rot scan reads, on this run and on every cache hit until a later fetch replaces it (section 3), so this step never deletes it. Its `.skf-` name, like the fetch folder's, marks it as SKF output to SKF's ownership checks, so it stays with the skill's forge folder: a purge of the whole skill deletes it and a rename moves it.
119
+
232
120
  **Error handling:**
233
121
 
234
- - If QMD indexing fails: log the error, note that temporal enrichment will be unavailable. Do not fail the workflow.
235
- - If registry update fails: log the error, continue. The collection may exist in QMD even if the registry entry failed.
236
- - If cleanup fails: log a warning and continue.
122
+ - If QMD indexing fails: log the error, note that temporal enrichment will be unavailable. Do not fail the workflow. The files stay in `{temporal_feeder}` for step 5c.
123
+ - If a registry command fails, or `{forgeTierRwHelper}` does not resolve: log the error, continue. The collection may exist in QMD even if the registry entry failed.
237
124
 
238
125
  Display brief confirmation:
239
126