bmad-module-skill-forge 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.nvmrc +1 -1
  3. package/docs/_data/pinned.yaml +1 -1
  4. package/package.json +2 -2
  5. package/src/knowledge/skill-lifecycle.md +1 -1
  6. package/src/module-help.csv +2 -2
  7. package/src/shared/scripts/skf-comention-pairs.py +350 -0
  8. package/src/shared/scripts/skf-count-tokens.py +222 -0
  9. package/src/shared/scripts/skf-detect-docs.py +232 -1
  10. package/src/shared/scripts/skf-detect-language.py +77 -0
  11. package/src/shared/scripts/skf-detect-tools.py +105 -2
  12. package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
  13. package/src/shared/scripts/skf-find-cycles.py +246 -0
  14. package/src/shared/scripts/skf-hash-content.py +487 -2
  15. package/src/shared/scripts/skf-manifest-ops.py +67 -1
  16. package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
  17. package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
  18. package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
  19. package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
  20. package/src/shared/scripts/skf-shard-body.py +461 -0
  21. package/src/shared/scripts/skf-skill-inventory.py +139 -3
  22. package/src/shared/scripts/skf-structural-diff.py +307 -67
  23. package/src/shared/scripts/skf-validate-brief-schema.py +39 -1
  24. package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
  25. package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
  26. package/src/shared/scripts/skf-validate-output.py +443 -10
  27. package/src/shared/scripts/skf-validate-pins.py +1 -1
  28. package/src/shared/scripts/skf-verify-no-trace.py +214 -0
  29. package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
  30. package/src/skf-analyze-source/SKILL.md +17 -32
  31. package/src/skf-analyze-source/customize.toml +2 -0
  32. package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
  33. package/src/skf-analyze-source/references/continue.md +8 -13
  34. package/src/skf-analyze-source/references/generate-briefs.md +28 -12
  35. package/src/skf-analyze-source/references/headless-contract.md +33 -0
  36. package/src/skf-analyze-source/references/health-check.md +1 -2
  37. package/src/skf-analyze-source/references/identify-units.md +25 -36
  38. package/src/skf-analyze-source/references/init.md +12 -25
  39. package/src/skf-analyze-source/references/map-and-detect.md +8 -20
  40. package/src/skf-analyze-source/references/recommend.md +4 -14
  41. package/src/skf-analyze-source/references/scan-project.md +3 -11
  42. package/src/skf-analyze-source/references/step-auto-scope.md +42 -180
  43. package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
  44. package/src/skf-audit-skill/SKILL.md +9 -6
  45. package/src/skf-audit-skill/customize.toml +2 -0
  46. package/src/skf-audit-skill/references/init.md +58 -32
  47. package/src/skf-audit-skill/references/re-index.md +15 -34
  48. package/src/skf-audit-skill/references/report.md +11 -58
  49. package/src/skf-audit-skill/references/semantic-diff.md +4 -22
  50. package/src/skf-audit-skill/references/severity-classify.md +53 -87
  51. package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
  52. package/src/skf-audit-skill/references/structural-diff.md +43 -68
  53. package/src/skf-brief-skill/SKILL.md +7 -36
  54. package/src/skf-brief-skill/assets/scope-templates.md +1 -1
  55. package/src/skf-brief-skill/customize.toml +14 -0
  56. package/src/skf-brief-skill/references/analyze-target.md +6 -14
  57. package/src/skf-brief-skill/references/confirm-brief.md +2 -14
  58. package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
  59. package/src/skf-brief-skill/references/gather-intent.md +19 -22
  60. package/src/skf-brief-skill/references/headless-args.md +1 -1
  61. package/src/skf-brief-skill/references/health-check.md +1 -22
  62. package/src/skf-brief-skill/references/invocation-contract.md +37 -0
  63. package/src/skf-brief-skill/references/scope-definition.md +4 -14
  64. package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
  65. package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
  66. package/src/skf-brief-skill/references/version-resolution.md +1 -1
  67. package/src/skf-brief-skill/references/write-brief.md +17 -10
  68. package/src/skf-campaign/SKILL.md +15 -84
  69. package/src/skf-campaign/customize.toml +2 -0
  70. package/src/skf-campaign/manifest.yaml +4 -0
  71. package/src/skf-campaign/references/campaign-contracts.md +67 -0
  72. package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
  73. package/src/skf-campaign/references/step-01-setup.md +4 -5
  74. package/src/skf-campaign/references/step-02-strategy.md +8 -3
  75. package/src/skf-campaign/references/step-03-pins.md +1 -1
  76. package/src/skf-campaign/references/step-04-provenance.md +1 -1
  77. package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
  78. package/src/skf-campaign/references/step-06-batch.md +7 -6
  79. package/src/skf-campaign/references/step-11-maintenance.md +2 -2
  80. package/src/skf-campaign/references/step-resume.md +9 -8
  81. package/src/skf-campaign/scripts/campaign-deps.py +10 -1
  82. package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
  83. package/src/skf-campaign/scripts/campaign-status.py +162 -0
  84. package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
  85. package/src/skf-create-skill/SKILL.md +14 -3
  86. package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
  87. package/src/skf-create-skill/assets/skill-sections.md +9 -100
  88. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
  89. package/src/skf-create-skill/customize.toml +13 -0
  90. package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
  91. package/src/skf-create-skill/references/compile.md +34 -47
  92. package/src/skf-create-skill/references/component-extraction.md +8 -35
  93. package/src/skf-create-skill/references/ecosystem-check.md +9 -25
  94. package/src/skf-create-skill/references/enrich.md +4 -17
  95. package/src/skf-create-skill/references/extract.md +14 -29
  96. package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
  97. package/src/skf-create-skill/references/extraction-patterns.md +7 -7
  98. package/src/skf-create-skill/references/generate-artifacts.md +9 -23
  99. package/src/skf-create-skill/references/health-check.md +1 -1
  100. package/src/skf-create-skill/references/load-brief.md +16 -18
  101. package/src/skf-create-skill/references/report.md +12 -15
  102. package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
  103. package/src/skf-create-skill/references/step-auto-shard.md +52 -25
  104. package/src/skf-create-skill/references/step-doc-rot.md +30 -12
  105. package/src/skf-create-skill/references/step-doc-sources.md +2 -2
  106. package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
  107. package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
  108. package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
  109. package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
  110. package/src/skf-create-skill/references/validate.md +59 -28
  111. package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
  112. package/src/skf-create-stack-skill/SKILL.md +15 -9
  113. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
  114. package/src/skf-create-stack-skill/customize.toml +10 -0
  115. package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
  116. package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
  117. package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
  118. package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
  119. package/src/skf-create-stack-skill/references/generate-output.md +24 -75
  120. package/src/skf-create-stack-skill/references/init.md +9 -81
  121. package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
  122. package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
  123. package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
  124. package/src/skf-create-stack-skill/references/report.md +27 -62
  125. package/src/skf-create-stack-skill/references/validate.md +35 -31
  126. package/src/skf-drop-skill/SKILL.md +20 -23
  127. package/src/skf-drop-skill/customize.toml +10 -0
  128. package/src/skf-drop-skill/references/execute.md +51 -31
  129. package/src/skf-drop-skill/references/headless-contract.md +18 -0
  130. package/src/skf-drop-skill/references/health-check.md +1 -13
  131. package/src/skf-drop-skill/references/report.md +42 -10
  132. package/src/skf-drop-skill/references/select.md +88 -48
  133. package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
  134. package/src/skf-export-skill/SKILL.md +17 -14
  135. package/src/skf-export-skill/assets/managed-section-format.md +2 -5
  136. package/src/skf-export-skill/customize.toml +10 -6
  137. package/src/skf-export-skill/references/generate-snippet.md +21 -35
  138. package/src/skf-export-skill/references/load-skill.md +44 -42
  139. package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
  140. package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
  141. package/src/skf-export-skill/references/package.md +27 -33
  142. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
  143. package/src/skf-export-skill/references/result-envelope.md +28 -0
  144. package/src/skf-export-skill/references/summary.md +11 -5
  145. package/src/skf-export-skill/references/token-report.md +42 -24
  146. package/src/skf-export-skill/references/update-context.md +32 -33
  147. package/src/skf-forger/SKILL.md +27 -39
  148. package/src/skf-forger/references/pipeline-mode.md +34 -0
  149. package/src/skf-forger/scripts/parse-pipeline.py +270 -0
  150. package/src/skf-quick-skill/SKILL.md +10 -60
  151. package/src/skf-quick-skill/customize.toml +9 -0
  152. package/src/skf-quick-skill/references/batch-mode.md +6 -4
  153. package/src/skf-quick-skill/references/compile.md +15 -52
  154. package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
  155. package/src/skf-quick-skill/references/finalize.md +12 -6
  156. package/src/skf-quick-skill/references/halt-contract.md +54 -0
  157. package/src/skf-quick-skill/references/health-check.md +3 -4
  158. package/src/skf-quick-skill/references/quick-extract.md +7 -18
  159. package/src/skf-quick-skill/references/registry-resolution.md +1 -23
  160. package/src/skf-quick-skill/references/resolve-target.md +27 -34
  161. package/src/skf-quick-skill/references/write-and-validate.md +10 -25
  162. package/src/skf-refine-architecture/SKILL.md +14 -7
  163. package/src/skf-refine-architecture/customize.toml +9 -0
  164. package/src/skf-refine-architecture/references/compile.md +9 -7
  165. package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
  166. package/src/skf-refine-architecture/references/health-check.md +1 -1
  167. package/src/skf-refine-architecture/references/improvements.md +9 -25
  168. package/src/skf-refine-architecture/references/init.md +9 -16
  169. package/src/skf-refine-architecture/references/issue-detection.md +10 -25
  170. package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
  171. package/src/skf-refine-architecture/references/report.md +13 -10
  172. package/src/skf-rename-skill/SKILL.md +11 -21
  173. package/src/skf-rename-skill/customize.toml +10 -0
  174. package/src/skf-rename-skill/references/execute.md +107 -78
  175. package/src/skf-rename-skill/references/exit-codes.md +12 -0
  176. package/src/skf-rename-skill/references/report.md +16 -8
  177. package/src/skf-rename-skill/references/select.md +69 -42
  178. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
  179. package/src/skf-setup/SKILL.md +20 -12
  180. package/src/skf-setup/customize.toml +10 -0
  181. package/src/skf-setup/references/auto-index.md +9 -10
  182. package/src/skf-setup/references/ccc-index.md +14 -15
  183. package/src/skf-setup/references/detect-and-tier.md +6 -8
  184. package/src/skf-setup/references/report.md +22 -31
  185. package/src/skf-setup/references/tier-rules.md +2 -32
  186. package/src/skf-setup/references/write-config.md +9 -10
  187. package/src/skf-test-skill/SKILL.md +10 -8
  188. package/src/skf-test-skill/customize.toml +2 -0
  189. package/src/skf-test-skill/references/coherence-check.md +19 -31
  190. package/src/skf-test-skill/references/coverage-check.md +109 -75
  191. package/src/skf-test-skill/references/detect-mode.md +10 -41
  192. package/src/skf-test-skill/references/external-validators.md +11 -19
  193. package/src/skf-test-skill/references/init.md +38 -20
  194. package/src/skf-test-skill/references/migration-section-rules.md +4 -4
  195. package/src/skf-test-skill/references/report.md +60 -40
  196. package/src/skf-test-skill/references/score.md +32 -64
  197. package/src/skf-test-skill/references/scoring-rules.md +6 -62
  198. package/src/skf-test-skill/references/source-access-protocol.md +6 -6
  199. package/src/skf-test-skill/references/step-hard-gate.md +1 -3
  200. package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
  201. package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
  202. package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
  203. package/src/skf-test-skill/scripts/compute-score.py +131 -1
  204. package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
  205. package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
  206. package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
  207. package/src/skf-update-skill/SKILL.md +17 -5
  208. package/src/skf-update-skill/customize.toml +14 -0
  209. package/src/skf-update-skill/references/detect-changes.md +19 -37
  210. package/src/skf-update-skill/references/health-check.md +3 -9
  211. package/src/skf-update-skill/references/init.md +39 -32
  212. package/src/skf-update-skill/references/manual-section-rules.md +1 -2
  213. package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
  214. package/src/skf-update-skill/references/merge.md +13 -57
  215. package/src/skf-update-skill/references/re-extract.md +12 -24
  216. package/src/skf-update-skill/references/report.md +14 -7
  217. package/src/skf-update-skill/references/validate.md +32 -30
  218. package/src/skf-update-skill/references/write.md +86 -51
  219. package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
  220. package/src/skf-verify-stack/SKILL.md +11 -21
  221. package/src/skf-verify-stack/customize.toml +12 -3
  222. package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
  223. package/src/skf-verify-stack/references/coverage.md +21 -10
  224. package/src/skf-verify-stack/references/exit-codes.md +14 -0
  225. package/src/skf-verify-stack/references/health-check.md +1 -1
  226. package/src/skf-verify-stack/references/init.md +20 -25
  227. package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
  228. package/src/skf-verify-stack/references/integrations.md +44 -49
  229. package/src/skf-verify-stack/references/report.md +28 -34
  230. package/src/skf-verify-stack/references/requirements.md +3 -3
  231. package/src/skf-verify-stack/references/synthesize.md +36 -39
  232. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
  233. package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
  234. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
  235. package/src/skf-rename-skill/references/rebuild-context.md +0 -110
@@ -24,6 +24,20 @@ descriptionGuardProbeOrder:
24
24
  frontmatterValidatorProbeOrder:
25
25
  - '{project-root}/_bmad/skf/shared/scripts/skf-validate-frontmatter.py'
26
26
  - '{project-root}/src/shared/scripts/skf-validate-frontmatter.py'
27
+ # Resolve `{shardBodyHelper}` by probing `{shardBodyProbeOrder}` in order
28
+ # (installed SKF module path first, src/ dev-checkout fallback); first existing
29
+ # path wins. §4 uses it as the deterministic selective splitter and reads its
30
+ # `tier1_preserved` field instead of counting Tier-1 headings pre/post by hand.
31
+ shardBodyProbeOrder:
32
+ - '{project-root}/_bmad/skf/shared/scripts/skf-shard-body.py'
33
+ - '{project-root}/src/shared/scripts/skf-shard-body.py'
34
+ # Resolve `{renderMetadataStatsHelper}` by probing `{renderMetadataStatsProbeOrder}`
35
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
36
+ # existing path wins. §7 uses it in --check mode to re-derive the metadata
37
+ # `stats` / `confidence_distribution` instead of re-doing the arithmetic by hand.
38
+ renderMetadataStatsProbeOrder:
39
+ - '{project-root}/_bmad/skf/shared/scripts/skf-render-metadata-stats.py'
40
+ - '{project-root}/src/shared/scripts/skf-render-metadata-stats.py'
27
41
  ---
28
42
 
29
43
  <!-- Config: communicate in {communication_language}. -->
@@ -111,14 +125,14 @@ If `restored: true` in the verify-restore output, apply §0's post-restore re-va
111
125
 
112
126
  **If skill-check was available:** Skip — already validated in step 2.
113
127
 
114
- **If skill-check NOT available (fallback):** Perform manual frontmatter compliance check:
128
+ **If skill-check not available (fallback):** Perform manual frontmatter compliance check:
115
129
 
116
130
  - [ ] Frontmatter present — file starts with `---` and has closing `---`
117
131
  - [ ] `name` field — present, non-empty, lowercase alphanumeric + hyphens only, 1-64 chars
118
132
  - [ ] `name` matches skill output directory name
119
133
  - [ ] `description` field — present, non-empty, 1-1024 characters
120
134
  - [ ] No unknown fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` permitted
121
- - [ ] `version` and `author` are NOT in frontmatter (they belong in metadata.json)
135
+ - [ ] `version` and `author` are not in frontmatter (they belong in metadata.json)
122
136
 
123
137
  If fails: auto-fix (deterministic), re-validate once, record result. If passes: record "Frontmatter: PASS".
124
138
 
@@ -144,13 +158,21 @@ If `restored: true` in the verify-restore output, apply §0's post-restore re-va
144
158
 
145
159
  **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).
146
160
 
147
- **FORBIDDEN:** Running `npx skill-check split-body --write` without prior selective extraction. The `split-body --write` command extracts ALL `##` sections top-to-bottom, destroying Tier 1 inline content that the two-tier design depends on. This command is a LAST RESORT only after selective split has been attempted and proven insufficient.
161
+ This selective split is deterministic run `{shardBodyHelper}` (the same splitter step 5b auto-shard uses) rather than counting and extracting by hand. **Resolve `{shardBodyHelper}`** from `{shardBodyProbeOrder}`; first existing path wins.
162
+
163
+ ```bash
164
+ uv run {shardBodyHelper} <staging-skill-dir>/SKILL.md --budget 400
165
+ ```
166
+
167
+ It extracts the largest `## Full` sections to `references/` until the body fits, rewrites each as a cross-reference blockquote through the atomic-write helper, and reports `sections_extracted`, `body_lines_after`, `tier1_preserved`, `xref_ok`, and `under_budget`.
148
168
 
149
- **If selective split alone does not bring body under the limit** (rare typically only occurs when Tier 1 itself exceeds 300 lines): reduce Tier 1 Key API Summary and Architecture at a Glance sections to fit within limits. Do NOT fall back to automated `split-body --write` to solve a Tier 1 sizing problem.
169
+ **Do not run `npx skill-check split-body --write` before selective extraction.** It extracts every `##` section top-to-bottom, destroying the Tier 1 inline content the two-tier design depends on a last resort used only after selective split has been attempted and proven insufficient.
150
170
 
151
- **Tier 1 preservation check:** After ANY split operation, verify that ALL of the following sections remain inline in SKILL.md (not moved to references/): Overview, Quick Start, Common Workflows, Key API Summary, Migration & Deprecation Warnings (if present), Key Types, Architecture at a Glance, CLI (if present), Scripts & Assets (if present), Manual Sections. If any Tier 1 section was moved to references/, restore it immediately and re-split targeting only Tier 2 sections.
171
+ **If selective split alone does not bring body under the limit** (the splitter reports `under_budget: false` rare, typically only when Tier 1 itself exceeds 300 lines): reduce Tier 1 Key API Summary and Architecture at a Glance sections to fit within limits. Do not fall back to automated `split-body --write` to solve a Tier 1 sizing problem.
152
172
 
153
- **Post-split Tier-1 count check (mandatory):** before invoking the splitter, count the Tier-1 `##`-level section headings present in SKILL.md (any heading whose title matches one of the Tier-1 names listed above) and store as `tier1_count_pre`. After the split completes, recount as `tier1_count_post`. **HALT** if `tier1_count_post < tier1_count_pre` with: "Split reduced Tier-1 section count from {pre} to {post}. Tier-1 sections must remain inline. Restoring from staging backup and aborting body split manual review required." Do not proceed past §4 — Tier-1 preservation is a hard invariant and a count drop indicates the splitter pulled an inline section into references/ regardless of the section-list check above (e.g., heading-text variation, capitalization, or the splitter's own heuristics).
173
+ **Tier 1 preservation check:** After any split operation, verify that all of the following Tier 1 sections remain inline in SKILL.md (not moved to references/): Overview, Quick Start, Common Workflows, Key API Summary, Migration & Deprecation Warnings (if present), Key Types, Architecture at a Glance, CLI (if present), Scripts & Assets (if present), Manual Sections. If any was moved to references/, restore it immediately and re-split targeting only Tier 2 sections.
174
+
175
+ **Post-split Tier-1 count check (mandatory):** do not recount Tier-1 headings by hand — consume the splitter's `tier1_preserved` field. `{shardBodyHelper}` compares the Tier-1 headings inline before extraction against those inline afterward and reports `tier1_preserved` (with any pulled headings in `tier1_missing`). Read it from the invocation above, or re-check any split's result with `uv run {shardBodyHelper} <staging-skill-dir>/SKILL.md --dry-run`. **HALT** if `tier1_preserved` is false with: "Split reduced Tier-1 section count (missing {tier1_missing}). Tier-1 sections must remain inline. Restoring from staging backup and aborting body split — manual review required." Do not proceed past §4 — Tier-1 preservation is a hard invariant and a `tier1_preserved: false` result means the splitter pulled an inline section into references/ regardless of the section-list check above (e.g., heading-text variation, capitalization, or the splitter's own heuristics).
154
176
 
155
177
  **Anchor validation and remediation:** After any split, verify that context-snippet section anchors (`#quick-start`, `#key-types`) still resolve to headings in SKILL.md. If an anchor no longer resolves (section was split out), restore that section to SKILL.md inline content — the context-snippet must always reference sections that exist in the main file.
156
178
 
@@ -189,8 +211,8 @@ Parse output for: `description_score`, `content_score`, `review_score`, `validat
189
211
  **Apply dismissal rules** in this order:
190
212
 
191
213
  1. **Check score thresholds** against the "Score Thresholds" table in `{tesslDismissalData}`. Most importantly:
192
- - If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): 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 the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or not — that 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. On successful recovery (finding cleared), log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
193
- - If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do NOT trigger the recover-then-halt path and do NOT halt.
214
+ - If tessl's output contains a `findings[]` entry with rule ID `description_field` (the deterministic angle-bracket / XML-tag validator): 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 the `description_field` finding being **absent** from the re-run as the only successful recovery outcome. If the finding persists on the re-run — whether the re-substitution improved the description or not — that 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. On successful recovery (finding cleared), log `description-recovery: applied ({count} substitutions)` in the evidence report under "Dismissed tessl suggestions" and continue suggestion iteration against the rerun's `judge_suggestions[]`.
215
+ - If the LLM-judge `description_score` is below 100 **but no `description_field` finding is present** (the deterministic validator PASSED): this is a soft discoverability signal (jargon density, trigger-term phrasing) from the judge's sub-scores, not a sanitizer bypass. Record `description_judge_score: {n}% (deterministic description_field validator PASSED)` as a warning in the evidence report and continue — do not trigger the recover-then-halt path and do not halt.
194
216
  - If `review_score < 60` or `content_score < 60`: record warnings in the evidence report, continue.
195
217
  2. **Iterate `judge_suggestions[]`.** For each suggestion:
196
218
  - Cross-reference against the rules in `{tesslDismissalData}` in order.
@@ -206,7 +228,7 @@ tessl installs automatically via `npx`. A missing tool is not an error — grace
206
228
 
207
229
  **If §6 produced no novel suggestions (all dismissed via `{tesslDismissalData}`) OR tessl was unavailable:** Skip this gate — auto-proceed.
208
230
 
209
- **GATE [default: S]** — If `{headless_mode}` is true AND §6 produced novel suggestions: auto-select [S] Skip (a headless run has no human to triage novel suggestions), record `"tessl suggestions: {N} novel suggestion(s) auto-skipped (headless)"` in the evidence report under "Dismissed tessl suggestions", log `"headless: auto-skip {N} novel tessl suggestion(s)"`, and append `{step: "validate", gate: "tessl-suggestions", decision: "S", value: "{N} novel auto-skipped", rationale: "headless mode — no human to triage novel tessl suggestions", timestamp: {ISO}}` to the in-context `headless_decisions[]` list (step 5 §7 reads it into the evidence-report `## Auto-Decisions` table). This is the one consequential auto-decision that drops human-relevant feedback, so it must leave an audit row rather than vanishing. Then auto-proceed to §7 — do NOT present the menu below.
231
+ **GATE [default: S]** — If `{headless_mode}` is true AND §6 produced novel suggestions: auto-select [S] Skip (a headless run has no human to triage novel suggestions), record `"tessl suggestions: {N} novel suggestion(s) auto-skipped (headless)"` in the evidence report under "Dismissed tessl suggestions", log `"headless: auto-skip {N} novel tessl suggestion(s)"`, and append `{step: "validate", gate: "tessl-suggestions", decision: "S", value: "{N} novel auto-skipped", rationale: "headless mode — no human to triage novel tessl suggestions", timestamp: {ISO}}` to the in-context `headless_decisions[]` list and, the moment it lands, append the same object as a JSON line to the durable audit sink `{sidecar_path}/auto-decisions.jsonl` (the on-landing append established at step 1 §3). §8 below reconciles the sink into the evidence-report `## Auto-Decisions` table — this is the last gate to fire; the earlier gates' rows are already in the sink and were rendered into the staged report at step 5 §7. This is the one consequential auto-decision that drops human-relevant feedback, so it must leave an audit row rather than vanishing. Then auto-proceed to §7 — do not present the menu below.
210
232
 
211
233
  **If §6 produced novel suggestions** (ones not matched by any dismissal rule) AND `{headless_mode}` is false, present them to the user:
212
234
 
@@ -234,14 +256,21 @@ tessl suggestions (novel — not matched by `{tesslDismissalData}`):
234
256
 
235
257
  ### 7. Validate metadata.json
236
258
 
237
- Cross-check metadata.json against extraction inventory:
238
- - `stats.exports_documented` / `stats.exports_public_api` / `stats.exports_internal` / `stats.exports_total` are accurate
239
- - `stats.public_api_coverage` and `stats.total_coverage` are correctly computed (null when denominator is 0)
240
- - `confidence_distribution.{t1, t1_low, t2, t3}` bin **per-export signature tiers** — each documented export lands in exactly one tier, so the four bins must **sum to `stats.exports_documented`**. Do NOT fold T2 annotation counts or T3 doc-item counts into the bins (a common error: binning ~8 annotations + ~80 doc items on top of 59 exports → sum 147 ≠ 59). `skf-test-skill` coverage-check §4b enforces this sum as an internal-consistency defect. Carve-outs mirror that consumer: for a **stack** skill the distribution bins constituents (sums to the constituent count, not `exports_documented`); for a **reference-app** it bins per-citation provenance entries (sums to the citation count, not `pattern_surfaces_documented`)
241
- - `spec_version` is "1.3"
242
- - If `scripts[]` or `assets[]` arrays present: verify `stats.scripts_count`/`stats.assets_count` match array lengths; verify `file_entries` count in provenance-map.json matches
259
+ **Re-derive the computed fields with `{renderMetadataStatsHelper}` in check mode** rather than re-doing the arithmetic by hand. Resolve `{renderMetadataStatsHelper}` from `{renderMetadataStatsProbeOrder}` (first existing path wins; HALT if neither resolves), then run it against the staged provenance-map and metadata.json:
260
+
261
+ ```bash
262
+ uv run {renderMetadataStatsHelper} <staging-skill-dir>/provenance-map.json \
263
+ --check <staging-skill-dir>/metadata.json
264
+ ```
265
+
266
+ The helper re-bins `entries[]` by `signature_source`, recomputes `exports_documented`, `exports_total`, and `public_api_coverage` / `total_coverage` (null when the denominator is 0), and cross-checks `stats.scripts_count` / `stats.assets_count` against the `scripts[]` / `assets[]` array lengths and the provenance-map `file_entries` counts. It takes the judgment values (`exports_public_api`, `exports_internal`, `effective_denominator`) from `metadata.json` itself and infers the shape from `scope_type` / `skill_type` (pass `--shape` to override). Parse the emitted JSON (rely on the JSON, not the exit code):
243
267
 
244
- Auto-fix any discrepancies (these are computed values).
268
+ - **`coherence.ok: true`** the computed fields are internally consistent; record "Metadata: PASS".
269
+ - **`coherence.ok: false`** — each `violations[]` entry is `{field, expected, actual}` where `expected` is the correct value. **Auto-fix each computed-value violation** (`field` starting `stats.` or `confidence_distribution.`) by setting that field in `metadata.json` to `expected` (write via `python3 {atomicWriteHelper} write --target <staging-skill-dir>/metadata.json`), leaving every other stats field — e.g. `stats.notes` on a reference app — untouched. Record "Metadata: auto-fixed {N} computed-value discrepanc(y|ies)" listing the fields. These are computed values, so the helper is authoritative — a `confidence_distribution` violation is the per-entry mis-binning compile.md §4 describes (T2 annotations + T3 doc items counted on top of the per-export tiers); the helper's per-entry counts replace them. Carve-outs are handled by `--shape`: a **stack** distribution sums to the constituent count and a **reference-app** distribution to the per-citation count, so those are consistent states, not violations. A `provenance.file_entries.*` violation is not a computed metadata field — it means the provenance-map `file_entries` and metadata counts disagree; record it as a warning for manual reconciliation rather than auto-editing the count.
270
+
271
+ Then verify the two fields the helper does not own (genuine constants/contract):
272
+ - `spec_version` is `"1.3"`.
273
+ - `scope_type` is present and equals the brief's `scope.type` verbatim.
245
274
 
246
275
  ### 8. Update Evidence Report
247
276
 
@@ -272,6 +301,18 @@ Add validation results to evidence-report content in context:
272
301
  - {warnings, security results, tessl scores and suggestions — or "skipped"}
273
302
  ```
274
303
 
304
+ **Auto-Decisions table (reconcile from the durable sink — idempotent):** all gates have now fired — every one appended its row to the on-disk sink `{sidecar_path}/auto-decisions.jsonl` as it landed (including §6b's tessl-suggestions row), step 5 §7 rendered the step 1–3d rows into the staged `<staging-skill-dir>/evidence-report.md`, and steps 7–9 add none. Reconcile: read the sink's JSON lines (the authoritative durable record — it survives any compaction of the in-context buffer), union them with both the `## Auto-Decisions` rows already in `<staging-skill-dir>/evidence-report.md` and the in-context `headless_decisions[]` buffer, keyed on `step`+`gate` so no decision is duplicated or dropped, and re-render the section from that union. Because the rows are recovered from the sink rather than from the possibly-compacted buffer, the audit table stays complete on a long headless run. Emit one row per entry:
305
+
306
+ ```
307
+ ## Auto-Decisions
308
+
309
+ | Step | Gate | Decision | Rationale | Timestamp |
310
+ |------|------|----------|-----------|-----------|
311
+ | {step} | {gate} | {decision}{value?} | {rationale} | {timestamp} |
312
+ ```
313
+
314
+ If the sink, the on-disk rows, and `headless_decisions[]` are all empty, keep the single line step 5 §7 emitted: `No auto-decisions — workflow ran interactively (or all gates had no match to auto-resolve).` This keeps the section always present so reviewers can tell "zero auto-decisions" apart from "section missing", and keeps the row count equal to `summary.auto_decision_count`.
315
+
275
316
  **Description Guard population:** if the §0 protocol fired during §2 (`skill-check --fix`) or §4 (`split-body`), fill the four Description Guard fields from context:
276
317
 
277
318
  - `Restored: true` when `description_guard_restored == true`, otherwise `false`.
@@ -281,17 +322,7 @@ Add validation results to evidence-report content in context:
281
322
 
282
323
  When `Restored: false`, the three follow-up fields are all `—` — this is the clean-run expected state.
283
324
 
284
- ### 9. Menu Handling Logic
285
-
286
- **Conditional interaction step.** If tessl produced suggestions, section 6b halts for user input. Otherwise, auto-proceed.
287
-
288
- After validation completes (including any user decisions from section 6b), immediately load, read entire file, then execute `{nextStepFile}`.
289
-
290
- - Tool unavailability is a skip, not a halt
291
- - Validation failures are warnings — proceed to artifact generation
292
- - tessl gate only triggers when suggestions exist — no gate for clean reviews or unavailable tools
293
-
294
- ## CRITICAL STEP COMPLETION NOTE
325
+ ### 9. Auto-Proceed
295
326
 
296
- ONLY WHEN validation is complete (or skipped) and evidence-report content is updated will you proceed to load `{nextStepFile}` for artifact generation.
327
+ Conditional interaction: §6b halts for user input only when tessl produced novel suggestions; otherwise the step auto-proceeds. After validation completes (including any §6b decisions), load `{nextStepFile}`, read it fully, then execute it. Tool unavailability and validation failures are skips and warnings, never halts.
297
328
 
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # ///
5
+ """Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2).
6
+
7
+ step 5c self-declares its correction scan "grep-based and deterministic — no AI
8
+ judgment is used for detection." This helper *is* that grep: it walks the
9
+ resolved feeder artifacts, matches every line against the fixed 13-row
10
+ correction-pattern table with case-insensitive substring containment (no regex,
11
+ no semantics), and emits the matches as JSON. It applies the one positional
12
+ filter the step documents — dropping matches that land inside the compiled
13
+ SKILL.md's own `## Migration & Deprecation Warnings` section (compile §4b
14
+ already authored those, so re-emitting them would be circular) — before it
15
+ returns. Running the scan here (instead of in-prompt) makes the "deterministic,
16
+ identical input → identical output" promise actually hold: the model no longer
17
+ hand-greps multi-KB artifacts.
18
+
19
+ The genuine judgment the step keeps in-prompt is untouched by this script:
20
+ enriching each match's `affected` symbol from surrounding context (§2) and
21
+ choosing where the `## CORRECTION` block goes (§3). The script emits every
22
+ deterministic field (`source`, `pattern`, `category`, `context_line`,
23
+ `line_number`); the prompt adds `affected`.
24
+
25
+ The pattern table and category labels are the contract in step-doc-rot.md §2 —
26
+ keep the two in lockstep.
27
+
28
+ CLI usage:
29
+ uv run scan-doc-rot.py --skill-md <staged SKILL.md> [FEEDER ...]
30
+ uv run scan-doc-rot.py --feeder evidence-report.md --feeder provenance-map.json
31
+
32
+ --skill-md the compiled/staged SKILL.md feeder (feeder #4); matches inside its
33
+ `## Migration & Deprecation Warnings` section are excluded. It is
34
+ also scanned like any other feeder.
35
+ FEEDER any other feeder artifact (evidence-report.md, provenance-map.json,
36
+ temporal-context files). Repeatable positionally or via --feeder.
37
+
38
+ Missing or empty files are skipped silently (not an error), matching §1's
39
+ "attempt to load; if it does not exist or is empty, skip it."
40
+
41
+ Output (stdout, one object):
42
+ {
43
+ "scanned": ["<path>", ...], # feeders that existed and were non-empty
44
+ "matches": [ # correction_matches[] (affected added in-prompt)
45
+ {"source": "<path>", "pattern": "deprecated", "category": "Deprecation",
46
+ "context_line": "<line text>", "line_number": <1-indexed int>},
47
+ ...
48
+ ],
49
+ "match_count": <int>,
50
+ "excluded_count": <int> # SKILL.md matches dropped by the §4b filter
51
+ }
52
+
53
+ Exit codes:
54
+ 0 — scan emitted successfully (including the zero-match case)
55
+ 1 — invalid arguments (no feeders supplied)
56
+ """
57
+
58
+ from __future__ import annotations
59
+
60
+ import argparse
61
+ import json
62
+ import re
63
+ import sys
64
+ from pathlib import Path
65
+
66
+ # Fixed correction-pattern table — mirrors step-doc-rot.md §2. (pattern, category)
67
+ # Order is the table order; scanning is per-pattern so overlapping patterns
68
+ # (e.g. "deprecated" ⊂ "@deprecated") each record their own hit, exactly as the
69
+ # 13-row table enumerates them.
70
+ PATTERN_TABLE: list[tuple[str, str]] = [
71
+ ("deprecated", "Deprecation"),
72
+ ("@deprecated", "Deprecation"),
73
+ ("breaking change", "Breaking change"),
74
+ ("BREAKING", "Breaking change"),
75
+ ("removed in", "Removal"),
76
+ ("was removed", "Removal"),
77
+ ("renamed to", "Rename"),
78
+ ("renamed from", "Rename"),
79
+ ("superseded by", "Supersession"),
80
+ ("replaced by", "Supersession"),
81
+ ("no longer supported", "End of life"),
82
+ ("migration required", "Migration"),
83
+ ("signature changed", "Signature change"),
84
+ ]
85
+
86
+ _MIGRATION_HEADING = re.compile(r"^\s*##\s+Migration\s*&\s*Deprecation Warnings", re.IGNORECASE)
87
+
88
+
89
+ def scan_text(text: str, source: str) -> list[dict]:
90
+ """Case-insensitive substring scan of `text` against PATTERN_TABLE.
91
+
92
+ Returns one record per (line, pattern) hit, in file order then table order.
93
+ line_number is 1-indexed. Pure — no I/O.
94
+ """
95
+ matches: list[dict] = []
96
+ for idx, line in enumerate(text.splitlines(), start=1):
97
+ lowered = line.lower()
98
+ for pattern, category in PATTERN_TABLE:
99
+ if pattern.lower() in lowered:
100
+ matches.append(
101
+ {
102
+ "source": source,
103
+ "pattern": pattern,
104
+ "category": category,
105
+ "context_line": line.strip(),
106
+ "line_number": idx,
107
+ }
108
+ )
109
+ return matches
110
+
111
+
112
+ def migration_section_range(skill_md_text: str) -> tuple[int, int] | None:
113
+ """1-indexed [start, end) line window of the `## Migration & Deprecation
114
+ Warnings` section, or None if the section is absent.
115
+
116
+ start = the heading line; end = the next level-2 (`## `) heading, or EOF.
117
+ A `### ` subsection inside the section does NOT close it (only a sibling
118
+ `## ` heading does), matching §2's "before the next `##` heading".
119
+ """
120
+ lines = skill_md_text.splitlines()
121
+ start = None
122
+ for idx, line in enumerate(lines, start=1):
123
+ if _MIGRATION_HEADING.match(line):
124
+ start = idx
125
+ break
126
+ if start is None:
127
+ return None
128
+ end = len(lines) + 1
129
+ for idx in range(start + 1, len(lines) + 1):
130
+ if lines[idx - 1].lstrip().startswith("## "):
131
+ end = idx
132
+ break
133
+ return (start, end)
134
+
135
+
136
+ def apply_migration_exclusion(
137
+ matches: list[dict], skill_md_source: str | None, skill_md_text: str | None
138
+ ) -> tuple[list[dict], int]:
139
+ """Drop matches whose source is the compiled SKILL.md and whose line sits
140
+ inside its Migration & Deprecation Warnings section. Returns (kept, dropped)."""
141
+ if skill_md_source is None or skill_md_text is None:
142
+ return matches, 0
143
+ window = migration_section_range(skill_md_text)
144
+ if window is None:
145
+ return matches, 0
146
+ start, end = window
147
+ kept, dropped = [], 0
148
+ for m in matches:
149
+ if m["source"] == skill_md_source and start <= m["line_number"] < end:
150
+ dropped += 1
151
+ continue
152
+ kept.append(m)
153
+ return kept, dropped
154
+
155
+
156
+ def _load(path: str) -> str | None:
157
+ """Read a feeder file as UTF-8. Returns None for missing/empty (skip, not
158
+ an error). UTF-8 avoids cp1252 mojibake on Windows."""
159
+ p = Path(path)
160
+ if not p.is_file():
161
+ return None
162
+ text = p.read_text(encoding="utf-8")
163
+ if not text.strip():
164
+ return None
165
+ return text
166
+
167
+
168
+ def scan_files(feeders: list[str], skill_md: str | None) -> dict:
169
+ """Scan every feeder + the skill-md feeder, apply the §4b exclusion, return
170
+ the result object. Deterministic: feeders scanned in the given order."""
171
+ scanned: list[str] = []
172
+ matches: list[dict] = []
173
+ skill_md_text = None
174
+
175
+ ordered = list(feeders)
176
+ if skill_md is not None:
177
+ ordered.append(skill_md)
178
+
179
+ for path in ordered:
180
+ text = _load(path)
181
+ if text is None:
182
+ continue
183
+ scanned.append(path)
184
+ if path == skill_md:
185
+ skill_md_text = text
186
+ matches.extend(scan_text(text, path))
187
+
188
+ matches, excluded = apply_migration_exclusion(matches, skill_md, skill_md_text)
189
+ return {
190
+ "scanned": scanned,
191
+ "matches": matches,
192
+ "match_count": len(matches),
193
+ "excluded_count": excluded,
194
+ }
195
+
196
+
197
+ def _build_parser() -> argparse.ArgumentParser:
198
+ parser = argparse.ArgumentParser(
199
+ prog="scan-doc-rot",
200
+ description=(
201
+ "Deterministic doc-rot correction-indicator scan (step-doc-rot.md §2): "
202
+ "case-insensitive substring match of feeder artifacts against the fixed "
203
+ "correction-pattern table, with the compiled SKILL.md's Migration & "
204
+ "Deprecation Warnings section excluded."
205
+ ),
206
+ formatter_class=argparse.RawDescriptionHelpFormatter,
207
+ )
208
+ parser.add_argument(
209
+ "positional_feeders",
210
+ nargs="*",
211
+ metavar="FEEDER",
212
+ help="Feeder artifact paths (evidence-report.md, provenance-map.json, temporal files).",
213
+ )
214
+ parser.add_argument(
215
+ "--feeder",
216
+ action="append",
217
+ default=[],
218
+ dest="feeders",
219
+ help="Feeder artifact path (repeatable). Equivalent to a positional FEEDER.",
220
+ )
221
+ parser.add_argument(
222
+ "--skill-md",
223
+ dest="skill_md",
224
+ default=None,
225
+ help="Compiled/staged SKILL.md feeder; its Migration & Deprecation Warnings "
226
+ "section is excluded from matches.",
227
+ )
228
+ return parser
229
+
230
+
231
+ def main(argv=None) -> int:
232
+ parser = _build_parser()
233
+ args = parser.parse_args(argv)
234
+ feeders = list(args.positional_feeders) + list(args.feeders)
235
+ if not feeders and args.skill_md is None:
236
+ parser.print_usage(file=sys.stderr)
237
+ print("error: supply at least one feeder path or --skill-md", file=sys.stderr)
238
+ return 1
239
+ result = scan_files(feeders, args.skill_md)
240
+ print(json.dumps(result, indent=2))
241
+ return 0
242
+
243
+
244
+ if __name__ == "__main__":
245
+ raise SystemExit(main())
@@ -12,6 +12,7 @@ Produces a consolidated stack skill documenting how libraries connect. **Code-mo
12
12
  ## Conventions
13
13
 
14
14
  - Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
15
+ - The `knowledge/` and `shared/` prefixes are the exception: they resolve from the **SKF module root** (`{project-root}/_bmad/skf/` when installed, `src/` during development), not the skill root — they point at module-shared reference docs (`knowledge/tool-resolution.md`, `knowledge/version-paths.md`) and scripts/schemas (`shared/references/…`) that live once at the module root, mirroring the resolution note `references/health-check.md` carries for `shared/health-check.md`.
15
16
  - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
16
17
  - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
17
18
  - `{project-root}`-prefixed paths resolve from the project working directory.
@@ -34,7 +35,9 @@ These rules apply to every step in this workflow:
34
35
 
35
36
  ## Workflow state contract
36
37
 
37
- Every step that emits a warning ("log a warning", "record in workflow state for the evidence report", "Warning: ...", etc.) appends a structured entry to a single in-memory list named `workflow_warnings[]`. Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Step 7 surfaces these in `evidence-report.md`; step 8 may add validation findings; step 9 §5 reads the accumulated list and renders the user-facing "Warnings" section. There is exactly one accumulator for the whole workflow — do not invent per-step channels.
38
+ Every step that emits a warning appends a structured entry to a single in-memory list named `workflow_warnings[]` (the one accumulator for the whole workflow). Each entry has the shape `{step: "step-NN", severity: "info|warn|error", code: "<short-slug>", message: "<human text>", context: {<optional fields>}}`. Step 7 surfaces these in `evidence-report.md`; step 8 may add validation findings; step 9 §5 reads the accumulated list and renders the user-facing "Warnings" section.
39
+
40
+ **Single-pass — no mid-run checkpoint.** State lives in memory until step 7 commits `provenance-map.json`/`evidence-report.md`; the workflow keeps no resumable checkpoint and On Activation does not probe for a prior run — the analysis is deterministic and cheap to redo, and the two gates are trivially re-confirmed. If interrupted before that commit, restart from step 1.
38
41
 
39
42
  ## Stages
40
43
 
@@ -65,14 +68,13 @@ Every step that emits a warning ("log a warning", "record in workflow state for
65
68
 
66
69
  Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
67
70
 
68
- | Code | Meaning | Raised by |
71
+ | Code | Meaning | Raised by (halt_reason) |
69
72
  | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
70
73
  | 0 | success | step 10 (terminal handoff to shared health-check) |
71
- | 2 | input-missing / input-invalid | step 1 §0 (`config.yaml` missing or malformed); step 2 §2 headless cannot proceed without manifests (S2); step 4 §3 all extractions failed (B7); step 5 §2 feasibility-report `schemaVersion` mismatch; compose-mode-rules `schemaVersion` mismatch |
72
- | 3 | resolution-failure | step 1 §1 (`forge-tier.yaml` missing); step 2 §0 compose-mode skill resolution corruption (manifest + symlink both fail); step 2 §0 compose-mode zero qualifying skills (S1/S3) |
73
- | 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path |
74
- | 5 | overwrite-cancelled | step 7 collision when the user declines to replace an existing stack package |
75
- | 6 | user-cancelled | any interactive menu in step 3 / step 6 (user selected `[X]` Cancel and exit) |
74
+ | 2 | input / precondition invalid | step 1 §0 `config.yaml` missing/malformed (`config-missing`); step 2 §2 headless with no manifests (`no-manifests`, S2); step 4 §3 all extractions failed (`all-extractions-failed`, B7); step 5 §2 feasibility-report `schemaVersion` mismatch (`schema-version-mismatch`) |
75
+ | 3 | resolution-failure | step 1 §1 `forge-tier.yaml` missing (`forge-tier-missing`); step 2 §0 compose-mode skill-resolution corruption (manifest + symlink both fail); step 2 §0 compose-mode zero qualifying skills (S1/B4); step 4 §0 compose-cycle — all `resolution-failure` |
76
+ | 4 | write-failure | step 7 §1 stage-dir / commit-dir failure; step 7 §1 group-dir collision when an existing non-stack skill occupies the target path — both `write-failure` |
77
+ | 6 | user-cancelled | any interactive menu in step 3 / step 6 when the user selects `[X]` Cancel and exit (`user-cancelled`) |
76
78
 
77
79
  ## Result Contract (Headless)
78
80
 
@@ -82,7 +84,7 @@ When `{headless_mode}` is true, step 9 emits a single-line JSON envelope on **st
82
84
  SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","skill_name":"…","stack_libraries":["…"],"mode":"code|compose","exit_code":0,"halt_reason":null}
83
85
  ```
84
86
 
85
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"forge-tier-missing"`, `"config-missing"`, `"no-manifests"`, `"all-extractions-failed"`, `"schema-version-mismatch"`, `"resolution-failure"`, `"write-failure"`, `"overwrite-cancelled"`, `"user-cancelled"`. `exit_code` matches the table above.
87
+ `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `skill_package` is the absolute path to the committed stack-skill directory (or `null` on error before commit). `skill_name` is the stack skill's published name (e.g. `{project_name}-stack`). `stack_libraries` is the array of library names included in the stack (constituent skill names in compose-mode, dependency names in code-mode). `mode` is `"code"` or `"compose"` per the run's resolved mode (`null` if the run halts before mode resolution). `halt_reason` is one of: `null` (success), `"config-missing"`, `"forge-tier-missing"`, `"no-manifests"`, `"all-extractions-failed"`, `"schema-version-mismatch"`, `"resolution-failure"`, `"write-failure"`, `"user-cancelled"`. `exit_code` matches the table above. Fields unknown at the halt point are `null` (`skill_name`) or `[]` (`stack_libraries`) — e.g. a `config-missing` halt precedes `project_name` resolution.
86
88
 
87
89
  ## On Activation
88
90
 
@@ -118,6 +120,10 @@ SKF_STACK_RESULT_JSON: {"status":"success|error","skill_package":"…|null","ski
118
120
  - `{composeModeRulesPath}` ← `workflow.compose_mode_rules_path` if non-empty, else `references/compose-mode-rules.md`
119
121
  - `{provenanceMapSchemaPath}` ← `workflow.provenance_map_schema_path` if non-empty, else `assets/provenance-map-schema.md`
120
122
 
121
- Stash all five as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` / `{integrationPatternsPath}` / `{manifestPatternsPath}` / `{composeModeRulesPath}` / `{provenanceMapSchemaPath}` directly no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default; non-empty values let orgs swap in house-style copies without forking the skill.
123
+ Also resolve `{onCompleteCommand}` `workflow.on_complete` if non-empty, else empty string (no-op`references/report.md` §6c skips the hook invocation entirely).
124
+
125
+ Stash all five paths plus `{onCompleteCommand}` as workflow-context variables. Stage files reference `{stackSkillTemplatePath}` / `{integrationPatternsPath}` / `{manifestPatternsPath}` / `{composeModeRulesPath}` / `{provenanceMapSchemaPath}` directly; empty-string overrides fall through to the bundled default.
126
+
127
+ Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents), then run `workflow.activation_steps_append` after.
122
128
 
123
129
  4. Load, read the full file, and then execute `references/init.md` to begin the workflow.
@@ -53,7 +53,7 @@ Used when the workflow ran in code-mode against an actual codebase. `source_repo
53
53
 
54
54
  ## Compose-mode variant
55
55
 
56
- Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `references/compose-mode-rules.md` (the integration tier is the weaker of the pair — never forced to a fixed band by detection method).
56
+ Used when the workflow ran in compose-mode against pre-generated constituent skills. Source-anchor fields (`source_repo`, `source_commit`, `source_ref`) are `null` because there is no codebase to anchor against — provenance traces back to the constituent skills instead, captured in the `constituents[]` array. Each entry's `extraction_method` is `"compose-from-skill"`; integrations have `detection_method` of `"architecture_co_mention"` (named in the architecture doc), `"constituent_documented_contract"` (a cross-library contract documented in a constituent skill's integration docs but not co-mentioned in the architecture document — e.g. a grep-verified upstream seam cited from a source skill), or `"inferred_from_shared_domain"` (synthesized inference from shared language/domain, no cited contract). `detection_method` records *how* an edge was discovered; it is orthogonal to `confidence`, which is inherited from the constituent skills per the Confidence Tier Inheritance matrix in `{composeModeRulesPath}` (the integration tier is the weaker of the pair — never forced to a fixed band by detection method).
57
57
 
58
58
  ```json
59
59
  {
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-create-stack-skill.
4
+ # Team overrides: _bmad/custom/skf-create-stack-skill.toml (under {project-root})
5
+ # Personal overrides: _bmad/custom/skf-create-stack-skill.user.toml (under {project-root})
4
6
 
5
7
  [workflow]
6
8
 
@@ -34,6 +36,14 @@ persistent_facts = [
34
36
  "file:{project-root}/**/project-context.md",
35
37
  ]
36
38
 
39
+ # Command invoked once the workflow reaches its terminal stage — after the
40
+ # stack package is committed and the result contract is written (step 9,
41
+ # report.md §6c), before the health-check chain. Override wins. Use for a
42
+ # terminal action such as catalog registration or notifying a downstream
43
+ # pipeline. Empty string = no terminal action. The hook never fails the run.
44
+
45
+ on_complete = ""
46
+
37
47
  # --- Optional asset overrides ---
38
48
  #
39
49
  # Lift the canonical asset paths so orgs can substitute house-style copies
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  nextStepFile: 'generate-output.md'
3
- stackSkillTemplate: 'assets/stack-skill-template.md'
4
3
  ---
5
4
 
6
5
  <!-- Config: communicate in {communication_language}. Artifact text in {document_output_language}. -->
@@ -13,7 +12,7 @@ Assemble the main SKILL.md by combining per-library extractions with the integra
13
12
 
14
13
  ## Rules
15
14
 
16
- - Compile SKILL.md following the stack-skill-template structure — integration patterns go first (primary value)
15
+ - Compile SKILL.md following the stack-skill-template structure — integration patterns go first
17
16
  - Do not write output files (Step 07)
18
17
  - Present compiled content for user review
19
18
 
@@ -21,7 +20,7 @@ Assemble the main SKILL.md by combining per-library extractions with the integra
21
20
 
22
21
  ### 1. Load Template Structure
23
22
 
24
- Load `{stackSkillTemplate}` and prepare SKILL.md section structure.
23
+ Load `{stackSkillTemplatePath}` and prepare SKILL.md section structure.
25
24
 
26
25
  ### 2. Generate Frontmatter
27
26
 
@@ -41,12 +40,12 @@ description: >
41
40
  **Frontmatter rules:**
42
41
 
43
42
  - `name`: lowercase alphanumeric + hyphens only, must match skill output directory name. **Stack skills MUST end in `-stack`** (e.g., `{project_name}-stack`) — this is how consumers (skf-verify-stack, skf-test-skill) detect stack vs individual skills.
44
- - `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{stackSkillTemplate}`.
43
+ - `description`: non-empty, max 1024 chars, trigger-optimized for agent discovery. MUST use third-person voice ("Processes..." not "I can..." or "You can..."). **Do NOT enumerate every library by name** — a 12+ library stack overruns 1024 chars. Keep the generic "{lib_count} libraries with {integration_count} integration patterns" form; if a per-library parenthetical is used, cap it to the top libraries by import/export count with a `+{N} more` suffix (full list lives in `metadata.json` `libraries[]`). See "Sizing Guidance for Large Stacks" in `{stackSkillTemplatePath}`.
45
44
  - No other frontmatter fields — only `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools` are permitted by spec
46
45
 
47
46
  ### 3. Compile Integration Layer
48
47
 
49
- **This is the core value of the stack skill.** Compile in order:
48
+ Compile in order:
50
49
 
51
50
  **Zero-integration guard:** If the integration graph from step 05 has zero edges (no detected integration pairs), skip the integration layer compilation and note: "No integration patterns detected — stack skill will contain library summaries without an integration layer." Proceed directly to section 4 (Per-Library Sections).
52
51
 
@@ -72,7 +71,7 @@ description: >
72
71
  `Per-Library Summaries` and `Library Reference Index` are the largest sections
73
72
  and grow with the stack. For a **large stack** (heuristic: **> 6 libraries OR
74
73
  > 6 integration patterns**), author both into `references/stack-catalog.md`
75
- (structure in `{stackSkillTemplate}`) and place only the inline pointer from the
74
+ (structure in `{stackSkillTemplatePath}`) and place only the inline pointer from the
76
75
  template's "Sizing Guidance" in SKILL.md — this keeps the body under the 500-line
77
76
  `body.max_lines` budget that step 08 enforces. For a **small stack**, keep both
78
77
  inline (inline passive context yields higher task accuracy). Either way, step 08's
@@ -132,9 +131,9 @@ Display: **Select:** [C] Continue to Output Generation | [X] Cancel and exit
132
131
 
133
132
  #### EXECUTION RULES:
134
133
 
135
- - ALWAYS halt and wait for user input after presenting compilation
134
+ - This is a review gate — advancing without the user's `C` would write output files from a compilation they never reviewed. Halt and wait for input after presenting the compiled SKILL.md.
136
135
  - **GATE [default: C]** — If `{headless_mode}`: auto-proceed with [C] Continue, log: "headless: auto-approve stack compilation"
137
- - ONLY proceed to next step when user approves and selects 'C'
136
+ - Proceed to the next step only once the user approves by selecting `C`.
138
137
 
139
138
  #### Menu Handling Logic:
140
139
 
@@ -6,18 +6,7 @@ Rules for synthesizing a stack skill from pre-generated individual skills and an
6
6
 
7
7
  ## Skill Loading
8
8
 
9
- Skills use version-nested directories see `knowledge/version-paths.md` for the full path templates and resolution rules.
10
-
11
- **Version-aware skill enumeration:**
12
-
13
- 1. **Primary: Export manifest** — Read `{skills_output_folder}/.export-manifest.json`. For each entry in `exports`, resolve the active version path: `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/` — this directory must contain both `SKILL.md` and `metadata.json`.
14
- 2. **Fallback: `active` symlinks** — If the manifest does not exist, is empty, or a manifest entry lacks an `active_version`, scan for `{skills_output_folder}/*/active/*/SKILL.md`. Each match resolves to a skill package at `{skills_output_folder}/{skill-name}/active/{skill-name}/` (the `{active_skill}` template).
15
- 3. **Filter:** Skip any skill named `{project_name}-stack` or any skill where `metadata.json` has `"skill_type": "stack"` — stack skills must not be loaded as source dependencies to avoid self-referencing loops
16
- 4. Skip any resolved skill package missing either `SKILL.md` or `metadata.json` — log a warning and skip
17
- 5. Load each `metadata.json` from the resolved version-aware path and extract: `name`, `language`, `confidence_tier`, `source_repo`, `exports` (count), `version`
18
- 6. Store the skill group directory name as `skill_dir` (the top-level name under `{skills_output_folder}`, distinct from `name` — the directory may differ from the metadata name)
19
- 7. Store the resolved package path as `skill_package_path` for use in later steps (extraction, integration detection)
20
- 8. Store loaded skills as `raw_dependencies` with `source: "existing_skill"`
9
+ The version-aware skill-enumeration protocol (export-manifest primary → active-symlink fallback → stack-skill filter → load metadata fields → store as `raw_dependencies`) is specified operationally in `detect-manifests.md` §0. That step runs at step 2 and has already enumerated and loaded every constituent skill into workflow state before this file is consulted at step 5, so the loading is not re-performed here — this file covers only the compose-mode composition rules below.
21
10
 
22
11
  ## Compose-mode Co-mention Precision
23
12
 
@@ -86,4 +75,4 @@ When no architecture document is available:
86
75
  - Mark all inferred integrations: `[inferred from shared domain]`
87
76
  - Inferred integrations default to lowest confidence of the pair with `[inferred from shared domain]` suffix (use this instead of `[composed]` for inferred integrations)
88
77
 
89
- **Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `assets/provenance-map-schema.md`) — NOT `inferred_from_shared_domain`. It is a cited contract, not a synthesized guess. Its confidence still inherits the weaker tier of the pair per the matrix above — detection method is orthogonal to tier and never forces a fixed band.
78
+ **Constituent-documented contracts (distinct from shared-domain inference):** When a constituent skill's own integration docs cite a verifiable cross-library contract (e.g. a grep-verified upstream seam) that the architecture document does not co-mention, record it with `detection_method: constituent_documented_contract` (see `{provenanceMapSchemaPath}`) — NOT `inferred_from_shared_domain`. It is a cited contract, not a synthesized guess. Its confidence still inherits the weaker tier of the pair per the matrix above — detection method is orthogonal to tier and never forces a fixed band.