bmad-module-skill-forge 1.4.1 → 1.5.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 (230) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +0 -8
  3. package/docs/_data/pinned.yaml +1 -1
  4. package/docs/_internal/STABILITY.md +1 -1
  5. package/docs/architecture.md +2 -2
  6. package/docs/examples.md +1 -1
  7. package/docs/workflows.md +5 -5
  8. package/package.json +2 -2
  9. package/src/README.md +1 -1
  10. package/src/knowledge/ccc-bridge.md +12 -12
  11. package/src/knowledge/qmd-registry.md +5 -5
  12. package/src/shared/health-check.md +5 -5
  13. package/src/shared/references/description-guard-protocol.md +100 -0
  14. package/src/shared/references/output-contract-schema.md +1 -1
  15. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +6 -6
  16. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +9 -3
  17. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +149 -0
  18. package/src/shared/scripts/schemas/skill-brief.v1.json +22 -2
  19. package/src/shared/scripts/skf-build-change-manifest.py +420 -0
  20. package/src/shared/scripts/skf-check-workspace-drift.py +321 -0
  21. package/src/shared/scripts/skf-compare-file-hashes.py +357 -0
  22. package/src/shared/scripts/skf-description-guard.py +359 -0
  23. package/src/shared/scripts/skf-detect-language.py +2 -2
  24. package/src/shared/scripts/skf-detect-scripts-assets.py +613 -0
  25. package/src/shared/scripts/skf-detect-tools.py +97 -8
  26. package/src/shared/scripts/skf-detect-workspaces.py +1 -1
  27. package/src/shared/scripts/skf-disqualify-candidates.py +576 -0
  28. package/src/shared/scripts/skf-emit-brief-result-envelope.py +3 -3
  29. package/src/shared/scripts/skf-emit-result-envelope.py +93 -9
  30. package/src/shared/scripts/skf-enumerate-stack-skills.py +514 -0
  31. package/src/shared/scripts/skf-forge-tier-rw.py +11 -11
  32. package/src/shared/scripts/skf-hash-content.py +284 -0
  33. package/src/shared/scripts/skf-load-provenance.py +295 -0
  34. package/src/shared/scripts/skf-merge-ccc-exclusions.py +34 -4
  35. package/src/shared/scripts/skf-pair-intersect.py +250 -0
  36. package/src/shared/scripts/skf-provenance-gap-dispatch.py +433 -0
  37. package/src/shared/scripts/skf-qmd-classify-collections.py +42 -8
  38. package/src/shared/scripts/skf-recommend-scope-type.py +2 -2
  39. package/src/shared/scripts/skf-render-quick-metadata.py +2 -2
  40. package/src/shared/scripts/skf-resolve-authoritative-files.py +504 -0
  41. package/src/shared/scripts/skf-scan-manifests.py +738 -0
  42. package/src/shared/scripts/skf-scan-skill-md-structure.py +375 -0
  43. package/src/shared/scripts/skf-update-active-symlink.py +315 -0
  44. package/src/shared/scripts/skf-validate-brief-inputs.py +5 -5
  45. package/src/shared/scripts/skf-validate-brief-schema.py +348 -0
  46. package/src/shared/scripts/skf-write-skill-brief.py +61 -5
  47. package/src/skf-analyze-source/SKILL.md +69 -16
  48. package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
  49. package/src/skf-analyze-source/customize.toml +55 -0
  50. package/src/skf-analyze-source/{steps-c/step-01b-continue.md → references/continue.md} +12 -12
  51. package/src/skf-analyze-source/{steps-c/step-06-generate-briefs.md → references/generate-briefs.md} +29 -10
  52. package/src/skf-analyze-source/{steps-c/step-07-health-check.md → references/health-check.md} +5 -3
  53. package/src/skf-analyze-source/{steps-c/step-03-identify-units.md → references/identify-units.md} +44 -14
  54. package/src/skf-analyze-source/{steps-c/step-01-init.md → references/init.md} +27 -6
  55. package/src/skf-analyze-source/{steps-c/step-04-map-and-detect.md → references/map-and-detect.md} +52 -25
  56. package/src/skf-analyze-source/{steps-c/step-05-recommend.md → references/recommend.md} +15 -8
  57. package/src/skf-analyze-source/{steps-c/step-02-scan-project.md → references/scan-project.md} +12 -10
  58. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -0
  59. package/src/skf-analyze-source/templates/analysis-report-template.md +6 -6
  60. package/src/skf-audit-skill/SKILL.md +66 -15
  61. package/src/skf-audit-skill/assets/drift-report-template.md +6 -5
  62. package/src/skf-audit-skill/customize.toml +49 -0
  63. package/src/skf-audit-skill/{steps-c/step-07-health-check.md → references/health-check.md} +5 -3
  64. package/src/skf-audit-skill/{steps-c/step-01-init.md → references/init.md} +57 -19
  65. package/src/skf-audit-skill/{steps-c/step-02-re-index.md → references/re-index.md} +11 -11
  66. package/src/skf-audit-skill/{steps-c/step-06-report.md → references/report.md} +25 -7
  67. package/src/skf-audit-skill/{steps-c/step-04-semantic-diff.md → references/semantic-diff.md} +6 -6
  68. package/src/skf-audit-skill/{steps-c/step-05-severity-classify.md → references/severity-classify.md} +5 -5
  69. package/src/skf-audit-skill/references/severity-rules.md +2 -0
  70. package/src/skf-audit-skill/{steps-c/step-03-structural-diff.md → references/structural-diff.md} +29 -17
  71. package/src/skf-brief-skill/SKILL.md +48 -18
  72. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  73. package/src/skf-brief-skill/assets/skill-brief-schema.md +32 -2
  74. package/src/skf-brief-skill/customize.toml +44 -0
  75. package/src/skf-brief-skill/{steps-c/step-02-analyze-target.md → references/analyze-target.md} +5 -5
  76. package/src/skf-brief-skill/{steps-c/step-04-confirm-brief.md → references/confirm-brief.md} +13 -10
  77. package/src/skf-brief-skill/references/draft-checkpoint.md +4 -4
  78. package/src/skf-brief-skill/{steps-c/step-01-gather-intent.md → references/gather-intent.md} +24 -24
  79. package/src/skf-brief-skill/references/headless-args.md +9 -9
  80. package/src/skf-brief-skill/references/headless-source-authority-detection.md +1 -1
  81. package/src/skf-brief-skill/{steps-c/step-06-health-check.md → references/health-check.md} +4 -2
  82. package/src/skf-brief-skill/references/portfolio-similarity-check.md +2 -2
  83. package/src/skf-brief-skill/references/qmd-collection-registration.md +1 -1
  84. package/src/skf-brief-skill/{steps-c/step-03-scope-definition.md → references/scope-definition.md} +19 -16
  85. package/src/skf-brief-skill/references/version-resolution.md +2 -2
  86. package/src/skf-brief-skill/{steps-c/step-05-write-brief.md → references/write-brief.md} +11 -10
  87. package/src/skf-create-skill/SKILL.md +25 -19
  88. package/src/skf-create-skill/assets/compile-assembly-rules.md +3 -3
  89. package/src/skf-create-skill/assets/skill-sections.md +5 -5
  90. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +11 -11
  91. package/src/skf-create-skill/customize.toml +35 -0
  92. package/src/skf-create-skill/references/authoritative-files-protocol.md +142 -0
  93. package/src/skf-create-skill/{steps-c/step-05-compile.md → references/compile.md} +16 -16
  94. package/src/skf-create-skill/{steps-c/step-03d-component-extraction.md → references/component-extraction.md} +16 -16
  95. package/src/skf-create-skill/{steps-c/step-02-ecosystem-check.md → references/ecosystem-check.md} +4 -4
  96. package/src/skf-create-skill/{steps-c/step-04-enrich.md → references/enrich.md} +3 -3
  97. package/src/skf-create-skill/{steps-c/step-03-extract.md → references/extract.md} +52 -120
  98. package/src/skf-create-skill/references/extraction-patterns.md +8 -8
  99. package/src/skf-create-skill/{steps-c/step-07-generate-artifacts.md → references/generate-artifacts.md} +11 -11
  100. package/src/skf-create-skill/{steps-c/step-09-health-check.md → references/health-check.md} +4 -2
  101. package/src/skf-create-skill/{steps-c/step-01-load-brief.md → references/load-brief.md} +40 -32
  102. package/src/skf-create-skill/{steps-c/step-08-report.md → references/report.md} +8 -8
  103. package/src/skf-create-skill/references/source-resolution-protocols.md +6 -6
  104. package/src/skf-create-skill/{steps-c/sub/step-02b-ccc-discover.md → references/sub/ccc-discover.md} +5 -5
  105. package/src/skf-create-skill/{steps-c/sub/step-03c-fetch-docs.md → references/sub/fetch-docs.md} +28 -12
  106. package/src/skf-create-skill/{steps-c/sub/step-03b-fetch-temporal.md → references/sub/fetch-temporal.md} +81 -50
  107. package/src/skf-create-skill/{steps-c/step-06-validate.md → references/validate.md} +46 -26
  108. package/src/skf-create-stack-skill/SKILL.md +76 -17
  109. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +102 -0
  110. package/src/skf-create-stack-skill/assets/stack-skill-template.md +1 -1
  111. package/src/skf-create-stack-skill/customize.toml +46 -0
  112. package/src/skf-create-stack-skill/{steps-c/step-06-compile-stack.md → references/compile-stack.md} +6 -5
  113. package/src/skf-create-stack-skill/references/compose-mode-rules.md +3 -1
  114. package/src/skf-create-stack-skill/{steps-c/step-05-detect-integrations.md → references/detect-integrations.md} +32 -11
  115. package/src/skf-create-stack-skill/{steps-c/step-02-detect-manifests.md → references/detect-manifests.md} +28 -28
  116. package/src/skf-create-stack-skill/{steps-c/step-07-generate-output.md → references/generate-output.md} +11 -85
  117. package/src/skf-create-stack-skill/{steps-c/step-10-health-check.md → references/health-check.md} +4 -2
  118. package/src/skf-create-stack-skill/{steps-c/step-01-init.md → references/init.md} +70 -4
  119. package/src/skf-create-stack-skill/references/integration-patterns.md +2 -0
  120. package/src/skf-create-stack-skill/references/manifest-patterns.md +2 -0
  121. package/src/skf-create-stack-skill/{steps-c/step-04-parallel-extract.md → references/parallel-extract.md} +40 -17
  122. package/src/skf-create-stack-skill/{steps-c/step-03-rank-and-confirm.md → references/rank-and-confirm.md} +5 -4
  123. package/src/skf-create-stack-skill/{steps-c/step-09-report.md → references/report.md} +3 -3
  124. package/src/skf-create-stack-skill/{steps-c/step-08-validate.md → references/validate.md} +6 -6
  125. package/src/skf-drop-skill/SKILL.md +84 -12
  126. package/src/skf-drop-skill/customize.toml +57 -0
  127. package/src/skf-drop-skill/{steps-c/step-02-execute.md → references/execute.md} +49 -34
  128. package/src/skf-drop-skill/{steps-c/step-04-health-check.md → references/health-check.md} +6 -4
  129. package/src/skf-drop-skill/{steps-c/step-03-report.md → references/report.md} +13 -5
  130. package/src/skf-drop-skill/{steps-c/step-01-select.md → references/select.md} +63 -15
  131. package/src/skf-export-skill/SKILL.md +85 -16
  132. package/src/skf-export-skill/assets/managed-section-format.md +3 -3
  133. package/src/skf-export-skill/assets/snippet-format.md +2 -2
  134. package/src/skf-export-skill/customize.toml +49 -0
  135. package/src/skf-export-skill/{steps-c/step-03-generate-snippet.md → references/generate-snippet.md} +11 -11
  136. package/src/skf-export-skill/{steps-c/step-07-health-check.md → references/health-check.md} +6 -4
  137. package/src/skf-export-skill/{steps-c/step-01-load-skill.md → references/load-skill.md} +11 -49
  138. package/src/skf-export-skill/references/manifest-rebuild.md +68 -0
  139. package/src/skf-export-skill/references/multi-skill-mode.md +38 -0
  140. package/src/skf-export-skill/references/orphan-context-detection.md +75 -0
  141. package/src/skf-export-skill/references/orphan-row-detection.md +102 -0
  142. package/src/skf-export-skill/{steps-c/step-02-package.md → references/package.md} +7 -7
  143. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +74 -0
  144. package/src/skf-export-skill/{steps-c/step-06-summary.md → references/summary.md} +20 -8
  145. package/src/skf-export-skill/{steps-c/step-05-token-report.md → references/token-report.md} +5 -5
  146. package/src/skf-export-skill/{steps-c/step-04-update-context.md → references/update-context.md} +109 -113
  147. package/src/skf-quick-skill/SKILL.md +63 -123
  148. package/src/skf-quick-skill/customize.toml +44 -0
  149. package/src/skf-quick-skill/references/batch-mode.md +102 -0
  150. package/src/skf-quick-skill/{steps-c/step-04-compile.md → references/compile.md} +10 -10
  151. package/src/skf-quick-skill/{steps-c/step-02-ecosystem-check.md → references/ecosystem-check.md} +5 -5
  152. package/src/skf-quick-skill/{steps-c/step-06-finalize.md → references/finalize.md} +7 -7
  153. package/src/skf-quick-skill/{steps-c/step-07-health-check.md → references/health-check.md} +3 -3
  154. package/src/skf-quick-skill/{steps-c/step-03-quick-extract.md → references/quick-extract.md} +5 -5
  155. package/src/skf-quick-skill/references/registry-resolution.md +2 -0
  156. package/src/skf-quick-skill/{steps-c/step-01-resolve-target.md → references/resolve-target.md} +14 -10
  157. package/src/skf-quick-skill/{steps-c/step-05-write-and-validate.md → references/write-and-validate.md} +4 -4
  158. package/src/skf-refine-architecture/SKILL.md +86 -16
  159. package/src/skf-refine-architecture/customize.toml +49 -0
  160. package/src/skf-refine-architecture/{steps-c/step-05-compile.md → references/compile.md} +4 -4
  161. package/src/skf-refine-architecture/{steps-c/step-02-gap-analysis.md → references/gap-analysis.md} +5 -5
  162. package/src/skf-refine-architecture/{steps-c/step-07-health-check.md → references/health-check.md} +6 -4
  163. package/src/skf-refine-architecture/{steps-c/step-04-improvements.md → references/improvements.md} +5 -5
  164. package/src/skf-refine-architecture/references/init.md +144 -0
  165. package/src/skf-refine-architecture/{steps-c/step-03-issue-detection.md → references/issue-detection.md} +5 -5
  166. package/src/skf-refine-architecture/references/refinement-rules.md +2 -0
  167. package/src/skf-refine-architecture/{steps-c/step-06-report.md → references/report.md} +14 -5
  168. package/src/skf-rename-skill/SKILL.md +82 -12
  169. package/src/skf-rename-skill/customize.toml +52 -0
  170. package/src/skf-rename-skill/{steps-c/step-02-execute.md → references/execute.md} +94 -99
  171. package/src/skf-rename-skill/references/health-check.md +30 -0
  172. package/src/skf-rename-skill/references/rebuild-context.md +110 -0
  173. package/src/skf-rename-skill/{steps-c/step-03-report.md → references/report.md} +13 -5
  174. package/src/skf-rename-skill/{steps-c/step-01-select.md → references/select.md} +59 -20
  175. package/src/skf-setup/SKILL.md +35 -35
  176. package/src/skf-setup/customize.toml +33 -0
  177. package/src/skf-setup/{steps-c/step-03-auto-index.md → references/auto-index.md} +10 -18
  178. package/src/skf-setup/{steps-c/step-01b-ccc-index.md → references/ccc-index.md} +16 -31
  179. package/src/skf-setup/{steps-c/step-01-detect-and-tier.md → references/detect-and-tier.md} +23 -10
  180. package/src/skf-setup/{steps-c/step-05-health-check.md → references/health-check.md} +1 -1
  181. package/src/skf-setup/{steps-c/step-04-report.md → references/report.md} +21 -19
  182. package/src/skf-setup/references/tier-rules.md +1 -1
  183. package/src/skf-setup/{steps-c/step-02-write-config.md → references/write-config.md} +14 -13
  184. package/src/skf-test-skill/SKILL.md +77 -15
  185. package/src/skf-test-skill/customize.toml +54 -0
  186. package/src/skf-test-skill/{steps-c/step-04-coherence-check.md → references/coherence-check.md} +28 -71
  187. package/src/skf-test-skill/{steps-c/step-03-coverage-check.md → references/coverage-check.md} +16 -39
  188. package/src/skf-test-skill/{steps-c/step-02-detect-mode.md → references/detect-mode.md} +4 -26
  189. package/src/skf-test-skill/{steps-c/step-04b-external-validators.md → references/external-validators.md} +6 -28
  190. package/src/skf-test-skill/references/health-check.md +14 -0
  191. package/src/skf-test-skill/{steps-c/step-01-init.md → references/init.md} +21 -37
  192. package/src/skf-test-skill/references/migration-section-rules.md +4 -2
  193. package/src/skf-test-skill/{steps-c/step-06-report.md → references/report.md} +41 -51
  194. package/src/skf-test-skill/{steps-c/step-05-score.md → references/score.md} +26 -43
  195. package/src/skf-test-skill/references/scoring-rules.md +8 -6
  196. package/src/skf-test-skill/references/source-access-protocol.md +7 -5
  197. package/src/skf-test-skill/scripts/compute-score.py +88 -17
  198. package/src/skf-test-skill/templates/test-report-template.md +13 -13
  199. package/src/skf-update-skill/SKILL.md +23 -14
  200. package/src/skf-update-skill/customize.toml +44 -0
  201. package/src/skf-update-skill/{steps-c/step-02-detect-changes.md → references/detect-changes.md} +176 -68
  202. package/src/skf-update-skill/references/health-check.md +32 -0
  203. package/src/skf-update-skill/{steps-c/step-01-init.md → references/init.md} +56 -8
  204. package/src/skf-update-skill/references/manual-section-rules.md +4 -0
  205. package/src/skf-update-skill/references/merge-conflict-rules.md +4 -0
  206. package/src/skf-update-skill/{steps-c/step-04-merge.md → references/merge.md} +8 -8
  207. package/src/skf-update-skill/{steps-c/step-03-re-extract.md → references/re-extract.md} +53 -44
  208. package/src/skf-update-skill/references/remote-source-resolution.md +5 -1
  209. package/src/skf-update-skill/references/report.md +214 -0
  210. package/src/skf-update-skill/{steps-c/step-05-validate.md → references/validate.md} +5 -5
  211. package/src/skf-update-skill/{steps-c/step-06-write.md → references/write.md} +73 -51
  212. package/src/skf-verify-stack/SKILL.md +89 -17
  213. package/src/skf-verify-stack/assets/feasibility-report-template.md +4 -4
  214. package/src/skf-verify-stack/customize.toml +50 -0
  215. package/src/skf-verify-stack/references/coverage-patterns.md +2 -2
  216. package/src/skf-verify-stack/{steps-c/step-02-coverage.md → references/coverage.md} +7 -7
  217. package/src/skf-verify-stack/{steps-c/step-07-health-check.md → references/health-check.md} +7 -5
  218. package/src/skf-verify-stack/references/init.md +166 -0
  219. package/src/skf-verify-stack/references/integration-verification-rules.md +1 -1
  220. package/src/skf-verify-stack/{steps-c/step-03-integrations.md → references/integrations.md} +12 -12
  221. package/src/skf-verify-stack/{steps-c/step-06-report.md → references/report.md} +22 -41
  222. package/src/skf-verify-stack/{steps-c/step-04-requirements.md → references/requirements.md} +7 -7
  223. package/src/skf-verify-stack/{steps-c/step-05-synthesize.md → references/synthesize.md} +8 -8
  224. package/tools/validate-docs-drift.js +1 -1
  225. package/src/skf-refine-architecture/steps-c/step-01-init.md +0 -136
  226. package/src/skf-rename-skill/steps-c/step-04-health-check.md +0 -22
  227. package/src/skf-test-skill/steps-c/step-07-health-check.md +0 -25
  228. package/src/skf-update-skill/steps-c/step-07-report.md +0 -148
  229. package/src/skf-update-skill/steps-c/step-08-health-check.md +0 -22
  230. package/src/skf-verify-stack/steps-c/step-01-init.md +0 -178
@@ -43,9 +43,9 @@ When `brief.target_version` is absent but `brief.version` is present AND `source
43
43
  3. **Resolution outcomes:**
44
44
  - **Single match:** Store the matched tag as `source_ref`. Use it as `{branch}` in all subsequent clone/API commands. Do not warn — this is the expected path.
45
45
  - **Multiple matches:** Present the matching tags to the user — "Multiple tags match `brief.version` ({brief.version}): {list}. Which one should I use, or fall back to HEAD?" Wait for selection.
46
- - **Zero matches:** ⚠️ Warn: "No git tag found matching `brief.version` ({brief.version}). Falling back to default branch — **extracted code may not match the declared version.** If you intended to pin a specific version, set `target_version` explicitly in the brief." Set `source_ref` to `HEAD` and proceed with default branch. Append `tag_resolution: {status: "fallback-head", requested: "{brief.version}", reason: "no-matching-tag"}` to the in-context evidence-report payload so step-05 §7 surfaces the fallback in the evidence report. This turns the warning into a persistent audit trail a reviewer can grep later, not just a one-shot stderr line.
46
+ - **Zero matches:** ⚠️ Warn: "No git tag found matching `brief.version` ({brief.version}). Falling back to default branch — **extracted code may not match the declared version.** If you intended to pin a specific version, set `target_version` explicitly in the brief." Set `source_ref` to `HEAD` and proceed with default branch. Append `tag_resolution: {status: "fallback-head", requested: "{brief.version}", reason: "no-matching-tag"}` to the in-context evidence-report payload so step 5 §7 surfaces the fallback in the evidence report. This turns the warning into a persistent audit trail a reviewer can grep later, not just a one-shot stderr line.
47
47
 
48
- 4. **Do not halt on zero matches.** Unlike the explicit path, implicit resolution never blocks compilation — `brief.version` is an auto-populated hint, and some repositories simply do not tag releases. The warning is sufficient notice; the evidence report in step-08 will surface the HEAD fallback for reviewers.
48
+ 4. **Do not halt on zero matches.** Unlike the explicit path, implicit resolution never blocks compilation — `brief.version` is an auto-populated hint, and some repositories simply do not tag releases. The warning is sufficient notice; the evidence report in step 8 will surface the HEAD fallback for reviewers.
49
49
 
50
50
  5. **Store `source_ref`** in context exactly as in the explicit path. It flows through to metadata.json and provenance-map.json so downstream workflows (update-skill, audit-skill) can re-clone from the same ref.
51
51
 
@@ -65,7 +65,7 @@ Implicit resolution via `brief.version` is **not applied to local sources** —
65
65
 
66
66
  ## Remote Source Resolution
67
67
 
68
- **Note:** Quick-tier remote sources do not use the workspace/clone protocol described below. Quick tier accesses remote files via the `gh_bridge.read_file` path described in step-03 section 4.
68
+ **Note:** Quick-tier remote sources do not use the workspace/clone protocol described below. Quick tier accesses remote files via the `gh_bridge.read_file` path described in step 3 section 4.
69
69
 
70
70
  If `source_repo` is a local path: proceed with the tier-appropriate strategy as normal.
71
71
 
@@ -84,7 +84,7 @@ If `source_repo` is a remote URL (GitHub URL or owner/repo format) AND tier is F
84
84
 
85
85
  3. **Workspace check — resolve the source locally:**
86
86
 
87
- **Concurrency guard:** all of the operations below (fetch, checkout, rev-parse, and the extraction read that follows in step-03) must be wrapped in an exclusive `flock` on `{workspace_repo_path}/.skf-workspace.lock`. Acquire the lock before the workspace-hit check, hold it across fetch + checkout + rev-parse, AND keep holding it through the extraction-time read of the working tree. Two concurrent batch runs that target the same workspace clone but different `source_ref` values would otherwise race — one would `checkout` while the other was reading files mid-extraction, corrupting the inventory. The lock makes the per-workspace-repo unit of work serial. Use `flock -x {lockfile} -c "..."` or `fcntl.flock(LOCK_EX)`. If `flock` is unavailable, log a warning ("Concurrency guard unavailable — concurrent forges against the same workspace repo may produce inconsistent extraction inventories") and proceed.
87
+ **Concurrency guard:** all of the operations below (fetch, checkout, rev-parse, and the extraction read that follows in step 3) must be wrapped in an exclusive `flock` on `{workspace_repo_path}/.skf-workspace.lock`. Acquire the lock before the workspace-hit check, hold it across fetch + checkout + rev-parse, AND keep holding it through the extraction-time read of the working tree. Two concurrent batch runs that target the same workspace clone but different `source_ref` values would otherwise race — one would `checkout` while the other was reading files mid-extraction, corrupting the inventory. The lock makes the per-workspace-repo unit of work serial. Use `flock -x {lockfile} -c "..."` or `fcntl.flock(LOCK_EX)`. If `flock` is unavailable, log a warning ("Concurrency guard unavailable — concurrent forges against the same workspace repo may produce inconsistent extraction inventories") and proceed.
88
88
 
89
89
  **If `{workspace_repo_path}/.git/` exists (workspace hit):**
90
90
 
@@ -217,7 +217,7 @@ Also store `source_ref` in context (from tag resolution above, or `HEAD` if no t
217
217
 
218
218
  **If `source_type: "docs-only"`:** skip this section — no source files exist to reconcile.
219
219
 
220
- After the source path is accessible (local path from step-01, or workspace/ephemeral clone from above), check whether the source contains a version identifier and reconcile it with `brief.version`. Look for the first matching version file in the resolved source path:
220
+ After the source path is accessible (local path from step 1, or workspace/ephemeral clone from above), check whether the source contains a version identifier and reconcile it with `brief.version`. Look for the first matching version file in the resolved source path:
221
221
 
222
222
  - Python: `pyproject.toml` (`[project] version`), `setup.py` (`version=`), `__version__` in `__init__.py`
223
223
  - JavaScript/TypeScript: `package.json` (`"version"`). **Monorepo resolution:** When multiple `package.json` files exist (workspace root + packages), resolve version using this priority:
@@ -232,7 +232,7 @@ After the source path is accessible (local path from step-01, or workspace/ephem
232
232
 
233
233
  ⚠️ Warn the user: "Brief version ({brief.version}) differs from source version ({source_version}). Using source version ({source_version})."
234
234
 
235
- Update the working version in context to the source version. Record the mismatch in context for the evidence report (step-08).
235
+ Update the working version in context to the source version. Record the mismatch in context for the evidence report (step 8).
236
236
 
237
237
  **If no version file is found or version cannot be extracted:** keep `brief.version` as-is. No warning needed.
238
238
 
@@ -1,12 +1,14 @@
1
1
  ---
2
- nextStepFile: '../step-03-extract.md'
2
+ nextStepFile: '../extract.md'
3
3
  ---
4
4
 
5
+ <!-- Config: communicate in {communication_language}. -->
6
+
5
7
  # Step 2b: CCC Semantic Discovery
6
8
 
7
9
  ## STEP GOAL:
8
10
 
9
- If tier is Forge+ or Deep AND ccc is available, perform a semantic discovery pass over the source code to identify the most relevant files for the skill being created. Store ranked discovery results in context to pre-rank the file extraction queue in step-03.
11
+ If tier is Forge+ or Deep AND ccc is available, perform a semantic discovery pass over the source code to identify the most relevant files for the skill being created. Store ranked discovery results in context to pre-rank the file extraction queue in step 3.
10
12
 
11
13
  For Quick and Forge tiers, or when ccc is unavailable, skip silently and proceed.
12
14
 
@@ -18,8 +20,6 @@ For Quick and Forge tiers, or when ccc is unavailable, skip silently and proceed
18
20
 
19
21
  ## MANDATORY SEQUENCE
20
22
 
21
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
22
-
23
23
  ### 1. Check Tier Eligibility
24
24
 
25
25
  **If tier is Quick or Forge:**
@@ -32,7 +32,7 @@ Check `tools.ccc` from forge-tier.yaml. If `tools.ccc` is false, set `{ccc_disco
32
32
 
33
33
  If `tools.ccc` is true, check the remote source guard **before** proceeding to section 2:
34
34
 
35
- **Remote source guard:** If `source_root` is a remote URL (GitHub repository — workspace clone or ephemeral clone happens in step-03), CCC cannot operate yet. Set `{ccc_discovery: []}` and display: "CCC discovery deferred — remote source will be indexed after clone in step-03." Auto-proceed to section 5 (step completion). Step-03 will detect the deferred scenario and run CCC discovery on the resolved clone (workspace or ephemeral) before AST extraction begins.
35
+ **Remote source guard:** If `source_root` is a remote URL (GitHub repository — workspace clone or ephemeral clone happens in step 3), CCC cannot operate yet. Set `{ccc_discovery: []}` and display: "CCC discovery deferred — remote source will be indexed after clone in step 3." Auto-proceed to section 5 (step completion). Step-03 will detect the deferred scenario and run CCC discovery on the resolved clone (workspace or ephemeral) before AST extraction begins.
36
36
 
37
37
  If `source_root` is a local path, continue to section 2.
38
38
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- nextStepFile: '../step-04-enrich.md'
2
+ nextStepFile: '../enrich.md'
3
3
  # Resolve `{atomicWriteHelper}` by probing `{atomicWriteProbeOrder}` in order
4
4
  # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
5
  # path wins. HALT if neither resolves.
@@ -8,6 +8,8 @@ atomicWriteProbeOrder:
8
8
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
9
9
  ---
10
10
 
11
+ <!-- Config: communicate in {communication_language}. -->
12
+
11
13
  # Step 3c: Fetch Remote Documentation
12
14
 
13
15
  ## STEP GOAL:
@@ -23,13 +25,11 @@ Fetch remote documentation from brief-specified URLs using whatever web fetching
23
25
 
24
26
  ## MANDATORY SEQUENCE
25
27
 
26
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
27
-
28
28
  ### 1. Check Eligibility
29
29
 
30
30
  Evaluate the following conditions. **If the condition fails, skip silently to section 7 (auto-proceed) with no output:**
31
31
 
32
- 1. **`doc_urls` is present in the brief data:** Check that `doc_urls` contains at least one URL entry from step-01 context. If `doc_urls` is absent or empty, skip silently.
32
+ 1. **`doc_urls` is present in the brief data:** Check that `doc_urls` contains at least one URL entry from step 1 context. If `doc_urls` is absent or empty, skip silently.
33
33
 
34
34
  No tier gate — if `doc_urls` are present, this step runs at Quick, Forge, and Deep tiers alike.
35
35
 
@@ -84,12 +84,28 @@ If neither trigger fires, keep the page content as-is and do NOT trigger subpage
84
84
 
85
85
  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`).
86
86
 
87
- 3. **Fetch top subpages:** Fetch up to **10** of the most relevant subpages. For each:
87
+ 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).
88
+
89
+ The parallel pattern depends on the fetch tool:
90
+
91
+ - **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.
92
+ - **Bash-driven tools** (`curl`, `wget`): use `xargs -P 4 -n 1` to fan out from a newline-separated subpage list. Example:
93
+
94
+ ```bash
95
+ printf '%s\n' "${subpages[@]}" | xargs -P 4 -n 1 -I {} bash -c '
96
+ url="{}"
97
+ safe=$(echo -n "$url" | sha256sum | cut -c1-12)
98
+ curl -sSL --max-time 30 "$url" > "{staging}/subpage-$safe.md" \
99
+ || echo "fetch failed: $url" > "{staging}/subpage-$safe.md"
100
+ '
101
+ ```
102
+
103
+ For each subpage (regardless of tool):
88
104
  - Use the same web fetching tool as the root URL
89
105
  - Store with the subpage URL as provenance: `[EXT:{subpage-url}]`
90
- - If a subpage fetch fails, skip it and continue
106
+ - If a subpage fetch fails, skip it and continue with the rest of the batch — do not halt the whole stage
91
107
 
92
- 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."
108
+ 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).
93
109
 
94
110
  **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).
95
111
 
@@ -110,8 +126,8 @@ Parse the successfully fetched markdown for:
110
126
 
111
127
  **Mode determines merge behavior:**
112
128
 
113
- - **`source_type: "docs-only"`** — The doc-fetch inventory IS the extraction inventory. It replaces the empty inventory from step-03, since there was no source code to extract from.
114
- - **`source_type: "source"` (supplemental mode)** — Merge T3 items into the existing extraction inventory from step-03.
129
+ - **`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.
130
+ - **`source_type: "source"` (supplemental mode)** — Merge T3 items into the existing extraction inventory from step 3.
115
131
 
116
132
  **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.
117
133
 
@@ -131,7 +147,7 @@ Parse the successfully fetched markdown for:
131
147
  1. Write fetched markdown files to a staging directory: `_bmad-output/{skill-name}-docs/`
132
148
  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.
133
149
  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.
134
- 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-03b §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).
150
+ 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).
135
151
 
136
152
  ```yaml
137
153
  - name: "{skill-name}-docs"
@@ -190,7 +206,7 @@ ONLY WHEN documentation is fetched and T3 items are merged into the extraction i
190
206
  - Existing T1/T1-low/T2 items never overridden by T3 data
191
207
  - Docs-only mode: doc-fetch inventory correctly replaces empty extraction inventory
192
208
  - Supplemental mode: T3 items merged into existing inventory respecting conflict rule
193
- - Auto-proceeded to step-04
209
+ - Auto-proceeded to step 4
194
210
 
195
211
  ### SYSTEM FAILURE:
196
212
 
@@ -199,6 +215,6 @@ ONLY WHEN documentation is fetched and T3 items are merged into the extraction i
199
215
  - Overriding existing higher-confidence extractions (T1, T1-low, T2) with T3 data
200
216
  - Hardcoding a specific fetching tool instead of being tool-agnostic
201
217
  - Hallucinating API details not found in the fetched content
202
- - Beginning compilation in this step (that is step-05)
218
+ - Beginning compilation in this step (that is step 5)
203
219
 
204
220
  **Master Rule:** Documentation fetching is best-effort T3 enrichment. Fetch what you can, cite everything as `[EXT:{url}]`, never override higher-confidence data, and move on. Failures degrade gracefully — they never block the skill compilation pipeline.
@@ -1,5 +1,5 @@
1
1
  ---
2
- nextStepFile: './step-03c-fetch-docs.md'
2
+ nextStepFile: 'fetch-docs.md'
3
3
  # Resolve `{atomicWriteHelper}` by probing `{atomicWriteProbeOrder}` in order
4
4
  # (installed SKF module path first, src/ dev-checkout fallback); first existing
5
5
  # path wins. HALT if neither resolves.
@@ -8,23 +8,23 @@ atomicWriteProbeOrder:
8
8
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
9
9
  ---
10
10
 
11
+ <!-- Config: communicate in {communication_language}. -->
12
+
11
13
  # Step 3b: Fetch Temporal Context
12
14
 
13
15
  ## STEP GOAL:
14
16
 
15
- 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-04 has historical data to search when annotating extracted functions with T2 provenance.
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.
16
18
 
17
19
  ## Rules
18
20
 
19
21
  - Deep tier only — Quick, Forge, and Forge+ tiers skip this step entirely and silently
20
22
  - GitHub repositories only — other source types degrade gracefully
21
23
  - Do not halt the workflow if fetching or indexing fails
22
- - Do not modify extraction data from step-03 — this step only creates QMD collections
24
+ - Do not modify extraction data from step 3 — this step only creates QMD collections
23
25
 
24
26
  ## MANDATORY SEQUENCE
25
27
 
26
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
27
-
28
28
  ### 1. Check Eligibility
29
29
 
30
30
  Evaluate the following conditions sequentially. **If ANY condition fails, skip silently to section 5 (auto-proceed) with no output:**
@@ -69,57 +69,65 @@ Create a staging directory: `_bmad-output/{skill-name}-temporal/`
69
69
 
70
70
  Resolve the `owner` and `repo` from `source_repo` (e.g., `acme/toolkit` from `https://github.com/acme/toolkit`).
71
71
 
72
- Execute the following fetches, writing output as markdown files to the staging directory. **If any individual fetch fails, log a warning and continue with the others:**
73
-
74
- 1. **Issues (last 100):** (rationale: 100 is `gh issue list`'s default max-per-page; a single paginated call captures recent activity without extra round trips or rate-limit pressure)
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.
75
73
 
76
- ```bash
77
- gh issue list -R {owner}/{repo} --state all --limit 100 --json number,title,state,labels,createdAt,closedAt,body | ...
78
- ```
74
+ ```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
95
+ ```
79
96
 
80
- Write to `{staging}/issues.md` — format as a markdown document with one section per issue (number, title, state, labels, body summary).
97
+ Per-call rationale:
81
98
 
82
- 2. **Merged PRs (last 100):** (rationale: same 100-per-page convention as issues captures the most recent merges in one API call)
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.
83
100
 
84
- ```bash
85
- gh pr list -R {owner}/{repo} --state merged --limit 100 --json number,title,mergedAt,labels,body | ...
86
- ```
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`.
87
102
 
88
- Write to `{staging}/prs.md` format as a markdown document with one section per PR.
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.
89
104
 
90
- 3. **Releases (last 10):** (rationale: 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)
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:
91
106
 
92
- **Note:** `gh release list --json` does **not** support the `body` field. Use a two-step approach: list tags first, then fetch each release individually with `--json` (which IS supported on `gh release view`).
107
+ If `{staging}/.release-tags.json` is empty (no releases), skip Step 2 and omit the releases section entirely. Otherwise:
93
108
 
94
109
  ```bash
95
- # Step 1: Get release tags (body NOT available here)
96
- gh release list -R {owner}/{repo} --limit 10 --json tagName,name,publishedAt
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"
97
124
  ```
98
125
 
99
- If Step 1 returns an empty array (no releases), skip Step 2 and omit the releases section entirely.
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."
100
127
 
101
- ```bash
102
- # Step 2: For EACH tagName from Step 1, fetch the full release
103
- gh release view {tagName} -R {owner}/{repo} --json tagName,name,publishedAt,body
104
- ```
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.
105
129
 
106
- Iterate over every `tagName` from Step 1's JSON array. **Append each release to `{staging}/releases.md` immediately after its `gh release view` call returns** — do not buffer the entire loop in memory and write once at the end. 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, rather than discarding all of them because the loop didn't reach its final write.
107
-
108
- Write an empty `{staging}/releases.md` with a header (`# Releases (partial if interrupted)`) before the loop, then append one `## {tagName} — {name} ({publishedAt})` section per successful fetch. Failed individual fetches get a one-line placeholder: `## {tagName} — fetch failed: {error}`.
109
-
110
- If `gh release view` fails for a specific tag, log a warning and skip that release — continue 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."
111
-
112
- Format each section as a markdown block with tag, name, date, and body.
113
-
114
- 4. **Changelog (if exists):**
115
-
116
- Check if `CHANGELOG.md` or `RELEASES.md` exists in the repository root:
117
-
118
- ```bash
119
- gh api repos/{owner}/{repo}/contents/CHANGELOG.md --jq '.content' | base64 -d
120
- ```
121
-
122
- If found, write to `{staging}/changelog.md`. If not found (404), skip silently.
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.
123
131
 
124
132
  #### 3b. Targeted Function Searches (Uses Extraction Inventory)
125
133
 
@@ -127,19 +135,42 @@ After the generic fetches above, perform **targeted searches** using the top-lev
127
135
 
128
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.
129
137
 
130
- **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-03 §5 so every tracked export gets one search.)
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.)
131
139
 
132
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.
133
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
+
134
144
  ```bash
135
- # safe_name = re.sub(r'[^A-Za-z0-9_]', '', function_name); skip if empty
136
- # --limit 5: top-5 issues per function keeps signal-to-noise high (most matches
137
- # below rank 5 are typically keyword coincidences, not targeted discussions) and
138
- # caps the total response size across 10 function fan-outs at 50 issues.
139
- gh search issues --repo {owner}/{repo} "{safe_name}" --limit 5 --json number,title,state,body
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
140
171
  ```
141
172
 
142
- Aggregate all targeted search results into a single file: `{staging}/targeted-issues.md`. Format as a markdown document with one section per function name, listing the matching issues/PRs found.
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.
143
174
 
144
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.
145
176
 
@@ -1,6 +1,7 @@
1
1
  ---
2
- nextStepFile: './step-07-generate-artifacts.md'
2
+ nextStepFile: 'generate-artifacts.md'
3
3
  tesslDismissalData: 'assets/tessl-dismissal-rules.md'
4
+ descriptionGuardProtocol: '{project-root}/src/shared/references/description-guard-protocol.md'
4
5
  # Resolve `{atomicWriteHelper}` by probing `{atomicWriteProbeOrder}` in order
5
6
  # (installed SKF module path first, src/ dev-checkout fallback); first existing
6
7
  # path wins. HALT if neither resolves — losing atomic-write guarantees is not
@@ -8,8 +9,18 @@ tesslDismissalData: 'assets/tessl-dismissal-rules.md'
8
9
  atomicWriteProbeOrder:
9
10
  - '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
10
11
  - '{project-root}/src/shared/scripts/skf-atomic-write.py'
12
+ # Resolve `{descriptionGuardHelper}` by probing `{descriptionGuardProbeOrder}`
13
+ # in order (installed SKF module path first, src/ dev-checkout fallback);
14
+ # first existing path wins. HALT if neither resolves — letting an external
15
+ # tool's rewrite of the description field stand would silently regress
16
+ # discovery quality.
17
+ descriptionGuardProbeOrder:
18
+ - '{project-root}/_bmad/skf/shared/scripts/skf-description-guard.py'
19
+ - '{project-root}/src/shared/scripts/skf-description-guard.py'
11
20
  ---
12
21
 
22
+ <!-- Config: communicate in {communication_language}. -->
23
+
13
24
  # Step 6: Validate
14
25
 
15
26
  ## STEP GOAL:
@@ -20,38 +31,19 @@ To validate the compiled SKILL.md content against the agentskills.io specificati
20
31
 
21
32
  - Focus only on validating compiled content against spec — only fix spec compliance issues
22
33
  - Validation and auto-fix modify files in the staging directory
23
- - `<staging-skill-dir>` resolves to `_bmad-output/{skill-name}/` as created by step-05. The directory name must match the skill's frontmatter `name` field exactly — `skill-check`'s `frontmatter.name_matches_directory` rule rejects any suffix.
34
+ - `<staging-skill-dir>` resolves to `_bmad-output/{skill-name}/` as created by step 5. The directory name must match the skill's frontmatter `name` field exactly — `skill-check`'s `frontmatter.name_matches_directory` rule rejects any suffix.
24
35
  - If skill-check unavailable: skip validation, add warning to evidence report
25
36
  - Ignore non-zero exit codes from skill-check if JSON output shows 0 errors
26
37
 
27
38
  ## MANDATORY SEQUENCE
28
39
 
29
- **CRITICAL:** Follow this sequence exactly. Do not skip, reorder, or improvise.
30
-
31
40
  ### 0. Description Guard Protocol
32
41
 
33
42
  **Used by:** §2 (`skill-check check --fix`), §4 (`split-body`), and any future tool invocation that may modify SKILL.md.
34
43
 
35
- External validators occasionally rewrite the frontmatter `description` field `skill-check --fix` may replace it with a generic or truncated version, and `split-body` may touch it during mechanical restructuring. The step-05 §2a compiled description is **authoritative**: it has already been sanitized of angle-bracket tokens and trigger-optimized for agent discovery. Losing it to a tool's well-meaning rewrite breaks discovery quality and re-introduces the angle-bracket failure mode.
36
-
37
- To prevent this, any tool invocation that may touch SKILL.md must run inside the following four-phase guard:
38
-
39
- 1. **Capture.** Before invoking the tool, read the current SKILL.md frontmatter and snapshot the exact `description` value into a local variable (e.g., `guarded_description`). Capture the in-context copy as well.
40
- 2. **Execute.** Run the tool as specified in its section.
41
- 3. **Verify.** After the tool completes, re-read the on-disk SKILL.md and compare its frontmatter `description` against `guarded_description` as **token streams**: split each string on whitespace (`str.split()` — any run of spaces/tabs/newlines collapses) and compare the resulting lists element-by-element. This catches content divergence (missing words, replaced phrases, truncation, angle-bracket re-introduction) while ignoring cosmetic whitespace changes that a tool may apply (trailing newline, re-wrapped quoted strings). Do NOT use a normalized-string equality — a tool that rewrites `"foo bar"` to `"foo bar"` (collapsed inner run) would trip a naive normalization even though no semantic content changed, and a tool that swapped one word would slip past a looser fuzzy match. Token-stream comparison is the sweet spot.
42
- 4. **Restore on divergence.** If the post-tool description differs from `guarded_description` in any way other than whitespace normalization, write `guarded_description` back to the on-disk SKILL.md frontmatter and update the in-context copy to match. Record `description_guard_restored: true` with the tool name in context for the evidence report.
43
- 5. **Re-validate restored description.** After a restore, run `uv run {project-root}/src/shared/scripts/skf-validate-frontmatter.py <staging-skill-dir>/SKILL.md` against the on-disk file to confirm the restored description still satisfies the frontmatter contract (length limits, forbidden tokens, required fields). The script declares pyyaml in its PEP 723 inline metadata; `uv run` resolves it automatically (per `docs/getting-started.md`'s uv prereq), while bare `python3` would `ModuleNotFoundError` on a fresh interpreter. Capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field: flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step-09 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
44
-
45
- **What counts as divergence:**
44
+ Load `{descriptionGuardProtocol}` for the full prose explanation of the four-phase guard (why it exists, what counts as divergence, why token-stream comparison is the right shape). The deterministic phases are executed via `{descriptionGuardHelper}` the calling sections (§2 and §4) invoke the helper at the capture and verify-restore points.
46
45
 
47
- - The description was replaced (different content).
48
- - The description was truncated (suffix missing).
49
- - Angle-bracket tokens were re-introduced (should never happen after step-05 §2a, but protect anyway).
50
- - The field was deleted entirely (extreme tool behavior).
51
-
52
- **What does NOT count as divergence:** whitespace-only differences (trailing newline, trimmed spaces) — treat as equivalent.
53
-
54
- **Why this is centralized:** previously, §2 and §4 each contained their own capture/verify/restore prose. Duplicated defensive code drifts: a fix in one section doesn't propagate to the other, and adding a new tool invocation in the future requires remembering to copy the pattern. Centralizing the protocol gives step-06 one place to update when external validator behavior changes.
46
+ **This skill's post-restore re-validation hook:** after `{descriptionGuardHelper}` reports `restored: true`, run `uv run {project-root}/src/shared/scripts/skf-validate-frontmatter.py <staging-skill-dir>/SKILL.md` and capture `schema_revalidation_result` in context. If the validator exits non-zero OR reports failure for the `description` field, flip the Schema result back to `FAIL` in the evidence report (overriding any prior PASS/WARN from §2), record `description_guard_revalidation: FAIL` with the validator's diagnostic message, and continue — do not halt (step 9 health-check and result contract still need to run so the failure is surfaced through the normal artifact path).
55
47
 
56
48
  ### 1. Check Tool Availability
57
49
 
@@ -86,7 +78,21 @@ This performs frontmatter validation, description quality checks, body limit enf
86
78
 
87
79
  **Parse the JSON output** for: `qualityScore` (0-100), `diagnostics[]` (remaining issues), `fixed[]` (auto-corrected issues).
88
80
 
89
- **Description Guard Protocol:** This invocation may modify SKILL.md (especially when `fixed[]` is non-empty). Wrap the `skill-check check --fix` call in the four-phase protocol defined in §0: capture `guarded_description` before the call, execute, verify against the post-tool description, and restore on divergence. If `fixed[]` is non-empty, also re-read the modified SKILL.md to sync the in-context copy before proceeding — this prevents silent divergence between the in-context and on-disk versions that step-07 will use for artifact generation.
81
+ **Description Guard Protocol:** This invocation may modify SKILL.md (especially when `fixed[]` is non-empty). Wrap the `skill-check check --fix` call in the four-phase guard defined in §0 by invoking `{descriptionGuardHelper}` at the capture and verify-restore points:
82
+
83
+ ```bash
84
+ # Phase 1 — capture before the tool call
85
+ uv run {descriptionGuardHelper} capture <staging-skill-dir>/SKILL.md
86
+ # stash the returned `description` as `guarded_description` in workflow context
87
+
88
+ # Phase 2 — run skill-check (see command block above)
89
+
90
+ # Phases 3+4 — verify and restore after the tool call
91
+ uv run {descriptionGuardHelper} verify-restore <staging-skill-dir>/SKILL.md \
92
+ --captured-description "{guarded_description}"
93
+ ```
94
+
95
+ If `restored: true` in the verify-restore output, apply §0's post-restore re-validation hook. If `fixed[]` was non-empty in the skill-check output, also re-read the modified SKILL.md to sync the in-context copy before proceeding — this prevents silent divergence between the in-context and on-disk versions that step 7 will use for artifact generation.
90
96
 
91
97
  **Note:** `skill-check` may return non-zero exit code even when `errorCount` is 0. Always rely on parsed JSON, not the shell exit code.
92
98
 
@@ -113,7 +119,21 @@ If fails: auto-fix (deterministic), re-validate once, record result. If passes:
113
119
 
114
120
  **If step 2 reported `body.max_lines` failure:**
115
121
 
116
- **Description Guard Protocol:** Split operations may rewrite the frontmatter. Wrap the split invocation in the four-phase protocol defined in §0 to capture `guarded_description` before the call, execute, verify, and restore on divergence.
122
+ **Description Guard Protocol:** Split operations may rewrite the frontmatter. Wrap the split invocation in the four-phase guard defined in §0:
123
+
124
+ ```bash
125
+ # Phase 1 — capture before the split
126
+ uv run {descriptionGuardHelper} capture <staging-skill-dir>/SKILL.md
127
+ # stash returned `description` as `guarded_description`
128
+
129
+ # Phase 2 — run the split (selective extraction or, last-resort, split-body --write)
130
+
131
+ # Phases 3+4 — verify and restore after the split
132
+ uv run {descriptionGuardHelper} verify-restore <staging-skill-dir>/SKILL.md \
133
+ --captured-description "{guarded_description}"
134
+ ```
135
+
136
+ If `restored: true` in the verify-restore output, apply §0's post-restore re-validation hook.
117
137
 
118
138
  **Mandatory approach — selective split:** Identify Tier 2 sections by their `## Full` heading prefix (e.g., `## Full API Reference`, `## Full Type Definitions`, `## Full Integration Patterns`). Extract ONLY those sections to `references/`, starting with the largest. Keep ALL Tier 1 content and any smaller sections inline. Inline passive context achieves 100% task accuracy vs 79% for on-demand retrieval (per Vercel research).
119
139
 
@@ -162,7 +182,7 @@ Parse output for: `description_score`, `content_score`, `review_score`, `validat
162
182
  **Apply dismissal rules** in this order:
163
183
 
164
184
  1. **Check score thresholds** against the "Score Thresholds" table in `{tesslDismissalData}`. Most importantly:
165
- - If `description_score < 100`: follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step-05 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat `description_score == 100` on the re-run as the only successful recovery outcome. ANY value strictly less than 100 on the re-run — including 99, intermediate-but-improved scores like 95 (formerly tolerated as "close enough"), or 0 — counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do NOT proceed to §6b, and do NOT downgrade the recovery to a warning. If the re-run produces `description_score == 100`, log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
185
+ - If `description_score < 100`: follow the **recover-then-halt** path defined by the `description-xml-tags-guarded-upstream` rule in `{tesslDismissalData}`. Re-apply step 5 §2a's `<`/`>` → `{`/`}` substitution in place on the staging SKILL.md frontmatter `description`, re-sync the in-context copy, and re-run `npx -y tessl skill review <staging-skill-dir>` once. **Re-run gate:** treat `description_score == 100` on the re-run as the only successful recovery outcome. ANY value strictly less than 100 on the re-run — including 99, intermediate-but-improved scores like 95 (formerly tolerated as "close enough"), or 0 — counts as recovery failure: halt with the original `description-xml-tags-guarded-upstream` failure message from `{tesslDismissalData}`, do NOT proceed to §6b, and do NOT downgrade the recovery to a warning. If the re-run produces `description_score == 100`, log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
166
186
  - If `review_score < 60` or `content_score < 60`: record warnings in the evidence report, continue.
167
187
  2. **Iterate `judge_suggestions[]`.** For each suggestion:
168
188
  - Cross-reference against the rules in `{tesslDismissalData}` in order.