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
@@ -1,7 +1,10 @@
1
1
  ---
2
2
  nextStepFile: 'step-doc-drift.md'
3
3
  outputFile: '{forge_version}/drift-report-{timestamp}.md'
4
- severityRulesFile: 'references/severity-rules.md'
4
+ severityRulesFile: '{severityRulesPath}'
5
+ severityClassifyProbeOrder:
6
+ - '{project-root}/_bmad/skf/shared/scripts/skf-severity-classify.py'
7
+ - '{project-root}/src/shared/scripts/skf-severity-classify.py'
5
8
  ---
6
9
 
7
10
  <!-- Config: communicate in {communication_language}. -->
@@ -10,82 +13,61 @@ severityRulesFile: 'references/severity-rules.md'
10
13
 
11
14
  ## STEP GOAL:
12
15
 
13
- Grade every drift finding from Steps 03 and 04 by severity level (CRITICAL/HIGH/MEDIUM/LOW) using the classification rules. Calculate the overall drift score and produce a categorized findings table with confidence tier labels.
16
+ Grade every drift finding from Steps 03 and 04 by severity (CRITICAL/HIGH/MEDIUM/LOW), derive the overall drift score, and produce a categorized findings table with confidence-tier labels.
14
17
 
15
18
  ## Rules
16
19
 
17
- - Only classify severity of existing findings — do not discover new drift items or suggest remediation
18
- - Classification must be deterministic apply {severityRulesFile} rules strictly
19
- - Use subprocess Pattern 3 when available; if unavailable, load rules and classify in main thread
20
+ - Only classify existing findings — do not discover new drift items or suggest remediation
21
+ - Reading each change and assigning its `type` / `category` is judgment; mapping those to a severity, reducing the set to the drift score, and counting per level are deterministic and delegated to the shared helper so the classification cannot drift from {severityRulesFile} between runs
22
+ - The confidence tier (T1 / T1-low / T2) travels with each finding from Steps 03/04 — the helper never touches it
20
23
 
21
24
  ## MANDATORY SEQUENCE
22
25
 
23
- ### 1. Load Severity Rules
26
+ ### 1. Collect and Categorize Findings
24
27
 
25
- Launch a subprocess (Pattern 3 data operations) that:
26
- 1. Loads {severityRulesFile}
27
- 2. Extracts classification criteria for each severity level
28
- 3. Returns structured rules to parent
28
+ Gather every drift item already recorded in the report:
29
29
 
30
- **If subprocess unavailable:** Load {severityRulesFile} directly in main thread.
30
+ **From ## Structural Drift (Step 03):** added, removed, changed, and moved exports (plus any Script/Asset Drift rows).
31
+ **From ## Semantic Drift (Step 04, Deep tier only):** new patterns, changed conventions, dependency shifts, deprecated patterns.
31
32
 
32
- **Rules summary:**
33
- - **CRITICAL:** Removed/renamed exports, changed signatures (breaking changes)
34
- - **HIGH:** New public API not in skill (>3), removed helpers used in patterns, deprecated APIs
35
- - **MEDIUM:** Implementation changes behind stable API, 1-3 new exports, moved functions
36
- - **LOW:** Style/convention changes, comments, whitespace, internal functions
33
+ For each finding, read {severityRulesFile} and assign the two interpretive fields the rules key on — this is the judgment step, where the nuance of the change lives:
37
34
 
38
- ### 2. Collect All Findings
35
+ - `type`: `removed` / `added` / `changed` / `moved` / `renamed` / `deprecated` / `semantic`
36
+ - `category`: what the change is about — e.g. `export`, `module`, `class`, `interface`, `signature`, `parameter_count`, `return_type`, `inheritance`, `internal_helper`, `default_value`, `required_parameter`, `implementation`, `optional_parameter`, `function` (for a move), or a LOW bucket (`style`, `convention`, `comment`, `documentation`, `whitespace`, `test`, `private`, `internal`). The category carries the interpretation: a removed helper referenced in a documented pattern is `internal_helper` (→ HIGH); a new private function is `private` (→ LOW).
39
37
 
40
- Gather all drift items from the report:
38
+ Build one JSON array of `{type, category, detail, confidence, file, line}` objects — carry each finding's `confidence`, `file`, `line`, and human `detail` through untouched.
41
39
 
42
- **From ## Structural Drift (Step 03):**
43
- - Added exports
44
- - Removed exports
45
- - Changed exports
40
+ ### 2. Classify, Score, and Count (deterministic)
46
41
 
47
- **From ## Semantic Drift (Step 04, if Deep tier):**
48
- - New patterns
49
- - Changed conventions
50
- - Dependency shifts
51
- - Deprecated patterns
42
+ Mapping type/category to a severity, reducing the severities to an overall drift score, and counting per level each have exactly one correct answer for a given finding set — delegate them to the shared helper, which encodes {severityRulesFile} directly.
52
43
 
53
- Count total findings to classify.
44
+ **Resolve `{severityClassifyHelper}`** from `{severityClassifyProbeOrder}`; first existing path wins.
54
45
 
55
- ### 3. Apply Severity Classification
46
+ Pipe the findings array from §1 to the helper on stdin:
56
47
 
57
- For EACH finding, apply the severity rules:
58
-
59
- **Structural findings classification:**
60
- - Removed export → CRITICAL (breaking: skill references something that no longer exists)
61
- - Changed signature → CRITICAL (breaking: skill documents wrong parameters/return type)
62
- - Renamed export → CRITICAL (breaking: skill references old name)
63
- - Moved export (same signature) → MEDIUM (non-breaking but location in skill is wrong)
64
- - Added export (>3 total) → HIGH (significant API surface not documented)
65
- - Added export (1-3 total) → MEDIUM (minor gap in coverage)
66
-
67
- **Semantic findings classification:**
68
- - Deprecated pattern still in skill → HIGH (skill teaches outdated approach)
69
- - Changed convention → MEDIUM (skill may use old style)
70
- - New pattern detected → MEDIUM (skill doesn't cover new approach)
71
- - Dependency shift → MEDIUM (skill may reference wrong dependencies)
48
+ ```bash
49
+ echo '{findings_json}' | uv run {severityClassifyHelper} -
50
+ ```
72
51
 
73
- Record for each finding: original finding + assigned severity level.
52
+ Parse the emitted JSON:
74
53
 
75
- ### 4. Calculate Overall Drift Score
54
+ ```json
55
+ {
56
+ "status": "ok",
57
+ "drift_score": "CLEAN|MINOR|SIGNIFICANT|CRITICAL",
58
+ "total_findings": N,
59
+ "by_severity": {"CRITICAL": N, "HIGH": N, "MEDIUM": N, "LOW": N},
60
+ "findings": [ {"type": "...", "category": "...", "detail": "...", "confidence": "...", "severity": "CRITICAL|HIGH|MEDIUM|LOW"} ]
61
+ }
62
+ ```
76
63
 
77
- Apply scoring rules from {severityRulesFile}:
64
+ Consume `by_severity`, `drift_score`, and each finding's assigned `severity` directly — do not recount or recompute the score in prose.
78
65
 
79
- | Score | Criteria |
80
- |-------|----------|
81
- | **CLEAN** | 0 findings at any level |
82
- | **MINOR** | LOW findings only, no MEDIUM+ |
83
- | **SIGNIFICANT** | Any MEDIUM or HIGH findings, no CRITICAL |
84
- | **CRITICAL** | Any CRITICAL findings present |
66
+ **If `uv`/the helper cannot execute** (e.g. claude.ai web): fall back to classifying in the main thread — apply {severityRulesFile}'s severity levels to each finding's type/category, then reduce with its Overall Drift Score table (CLEAN = no findings; MINOR = LOW only; SIGNIFICANT = any MEDIUM/HIGH, no CRITICAL; CRITICAL = any CRITICAL present).
85
67
 
86
- ### 5. Compile Severity Classification Section
68
+ ### 3. Compile Severity Classification Section
87
69
 
88
- **Rollup inherits from step 3.** If step 3 §5 collapsed ≥ 10 same-kind findings into a single rollup row (deleted source file, renamed module, entire package tree removed), carry that rollup through to the matching severity table as one row — do not re-expand it here. Keep the existing 6-column severity table shape; the rollup encodes root cause, count, and representative symbols **inline in the `Finding` cell** rather than adding new columns, so rollup and per-item rows render cleanly in one table. Changed-signature and cross-file findings remain per-row; they were not eligible for rollup in step 3 and are not eligible here.
70
+ **Rollup inherits from step 3.** If step 3 §5 collapsed ≥ 10 same-kind findings into a single rollup row (deleted source file, renamed module, entire package tree removed), carry that rollup through to the matching severity table as one row — do not re-expand it here. Keep the 6-column severity table shape; the rollup encodes root cause, count, and representative symbols **inline in the `Finding` cell** rather than adding columns, so rollup and per-item rows render cleanly in one table. Changed-signature and cross-file findings remain per-row; they were not eligible for rollup in step 3 and are not eligible here.
89
71
 
90
72
  **Rollup row form (any severity table):**
91
73
 
@@ -93,32 +75,32 @@ Apply scoring rules from {severityRulesFile}:
93
75
  |---|---------|------|--------|----------|------------|
94
76
  | N | {root cause} (×{Count}; rep: `{sym1}`, `{sym2}`, `{sym3}`, …) | {structural/semantic} | {shared detail} | {root-cause path} | {T1/T2} |
95
77
 
96
- Append to {outputFile}:
78
+ Append to {outputFile}, filling the counts from `by_severity` and each finding's assigned `severity`:
97
79
 
98
80
  ```markdown
99
81
  ## Severity Classification
100
82
 
101
- **Overall Drift Score: {CLEAN / MINOR / SIGNIFICANT / CRITICAL}**
83
+ **Overall Drift Score: {drift_score}**
102
84
 
103
- ### CRITICAL ({count})
85
+ ### CRITICAL ({by_severity.CRITICAL})
104
86
 
105
87
  | # | Finding | Type | Detail | Location | Confidence |
106
88
  |---|---------|------|--------|----------|------------|
107
89
  | 1 | {finding} | {structural/semantic} | {detail} | {file}:{line} | {T1/T2} |
108
90
 
109
- ### HIGH ({count})
91
+ ### HIGH ({by_severity.HIGH})
110
92
 
111
93
  | # | Finding | Type | Detail | Location | Confidence |
112
94
  |---|---------|------|--------|----------|------------|
113
95
  | 1 | {finding} | {structural/semantic} | {detail} | {file}:{line} | {T1/T2} |
114
96
 
115
- ### MEDIUM ({count})
97
+ ### MEDIUM ({by_severity.MEDIUM})
116
98
 
117
99
  | # | Finding | Type | Detail | Location | Confidence |
118
100
  |---|---------|------|--------|----------|------------|
119
101
  | 1 | {finding} | {structural/semantic} | {detail} | {file}:{line} | {T1/T2} |
120
102
 
121
- ### LOW ({count})
103
+ ### LOW ({by_severity.LOW})
122
104
 
123
105
  | # | Finding | Type | Detail | Location | Confidence |
124
106
  |---|---------|------|--------|----------|------------|
@@ -126,35 +108,19 @@ Append to {outputFile}:
126
108
 
127
109
  ### Classification Summary
128
110
 
129
- | Severity | Count | Source |
130
- |----------|-------|--------|
131
- | CRITICAL | {count} | {structural: N, semantic: N} |
132
- | HIGH | {count} | {structural: N, semantic: N} |
133
- | MEDIUM | {count} | {structural: N, semantic: N} |
134
- | LOW | {count} | {structural: N, semantic: N} |
135
- | **Total** | {total} | |
111
+ | Severity | Count |
112
+ |----------|-------|
113
+ | CRITICAL | {by_severity.CRITICAL} |
114
+ | HIGH | {by_severity.HIGH} |
115
+ | MEDIUM | {by_severity.MEDIUM} |
116
+ | LOW | {by_severity.LOW} |
117
+ | **Total** | {total_findings} |
136
118
  ```
137
119
 
138
- ### 6. Update Report and Auto-Proceed
120
+ ### 4. Update Report and Auto-Proceed
139
121
 
140
122
  Update {outputFile} frontmatter:
141
123
  - Append `'severity-classify'` to `stepsCompleted`
142
- - Set `drift_score` to calculated overall score
143
-
144
- ### 7. Present MENU OPTIONS
145
-
146
- Display: "**Severity classification complete. Overall drift score: {score}. Proceeding to report generation...**"
147
-
148
- #### Menu Handling Logic:
149
-
150
- - After severity classification section is appended and frontmatter updated, immediately load, read entire file, then execute {nextStepFile}
151
-
152
- #### EXECUTION RULES:
153
-
154
- - This is an auto-proceed analysis step with no user choices
155
- - Proceed directly to next step after completion
156
-
157
- ## CRITICAL STEP COMPLETION NOTE
158
-
159
- ONLY WHEN the ## Severity Classification section has been appended to {outputFile} with all findings classified will you then load and read fully `{nextStepFile}` to execute and begin final report generation.
124
+ - Set `drift_score` to `{drift_score}` from the helper
160
125
 
126
+ Once the ## Severity Classification section has been appended with all findings classified, load, read fully, and execute `{nextStepFile}` (documentation drift).
@@ -1,6 +1,13 @@
1
1
  ---
2
2
  nextStepFile: 'report.md'
3
3
  outputFile: '{forge_version}/drift-report-{timestamp}.md'
4
+ # Resolve `{compareDocHashesHelper}` by probing `{compareDocHashesProbeOrder}`
5
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
6
+ # existing path wins. When neither resolves (uv/python absent), §2 falls back to
7
+ # skipping the doc-drift check with a notice — see the graceful-failure rule.
8
+ compareDocHashesProbeOrder:
9
+ - '{project-root}/_bmad/skf/shared/scripts/skf-detect-docs.py'
10
+ - '{project-root}/src/shared/scripts/skf-detect-docs.py'
4
11
  ---
5
12
 
6
13
  <!-- Config: communicate in {communication_language}. -->
@@ -53,15 +60,38 @@ Set `doc_drift_summary = { total_tracked: 0, skipped_entirely: false }` in workf
53
60
 
54
61
  ### 2. Fetch and Hash Each Tracked Doc
55
62
 
56
- For each entry in `doc_sources`:
63
+ Fetching each URL, hashing the response bytes, comparing against the stored `content_hash`, and categorizing the outcome is deterministic work — and the model cannot compute a `sha256:{hexdigest}` of fetched bytes natively (it must shell out). Delegate the whole fetch/hash/compare pass to the shared script, which hashes byte-symmetrically with how `doc_sources` hashes were written at compile time and fetches independently of whether a WebFetch tool is wired.
57
64
 
58
- 1. Read `url` and `content_hash` from the entry
59
- 2. **If `content_hash` is `null`:** Skip this entry — there is no baseline to compare against. Record it for the report as a null-hash entry. Do not attempt to fetch the URL.
60
- 3. **If `content_hash` is non-null:** Attempt HTTP GET of the URL with a reasonable timeout (10s)
61
- - **On success:** Compute `sha256:{hexdigest}` of the response body bytes (UTF-8 encoding). Compare against the stored `content_hash`. If they differ, record as drifted. If they match, record as unchanged.
62
- - **On failure (network error, timeout, non-200 status):** Record the entry as `status: "fetch_failed"` with the failure reason. Do not report as drift.
65
+ **Resolve `{compareDocHashesHelper}`** from `{compareDocHashesProbeOrder}`; first existing path wins.
63
66
 
64
- If URL fetching is unavailable in the current environment, skip doc drift check entirely with:
67
+ Run one deterministic comparison subprocess over the skill's `doc_sources`. The script reads the `doc_sources` array straight out of the skill's `metadata.json` (or accepts a bare `doc_sources` array, or `-` for stdin):
68
+
69
+ ```bash
70
+ uv run {compareDocHashesHelper} compare-hashes {skill_path}/metadata.json
71
+ ```
72
+
73
+ For each entry, the script:
74
+ - **`content_hash` is `null`** → records it under `skipped_null_hash` and does **not** fetch the URL (there is no baseline to compare against).
75
+ - **`content_hash` is non-null** → HTTP GETs the URL (15s timeout, same User-Agent as the compile side), computes `sha256:{hexdigest}` of the response body bytes, and compares against the stored `content_hash` (prefix-normalized so a bare-hex writer form still matches).
76
+ - Hashes match → `unchanged`.
77
+ - Hashes differ → `changed`, carrying `old_hash` and `new_hash`.
78
+ - Network error, timeout, or non-200 status → `fetch_failed`, carrying the failure `reason`. Not reported as drift.
79
+
80
+ Parse the emitted JSON:
81
+
82
+ ```json
83
+ {
84
+ "changed": [{"url": "...", "old_hash": "sha256:...", "new_hash": "sha256:..."}],
85
+ "unchanged": [{"url": "..."}],
86
+ "fetch_failed": [{"url": "...", "old_hash": "sha256:...", "reason": "..."}],
87
+ "skipped_null_hash": [{"url": "..."}],
88
+ "stats": {"total_tracked": N, "changed": N, "unchanged": N, "fetch_failed": N, "skipped_null_hash": N}
89
+ }
90
+ ```
91
+
92
+ The script exits 0 on any well-formed input (even when everything drifted) and exits 2 only on malformed args/JSON — it never blocks this informational audit.
93
+
94
+ If `uv`/`python` is unavailable or the script cannot be resolved — i.e. URL fetching is unavailable in the current environment — skip the doc drift check entirely with:
65
95
 
66
96
  ```markdown
67
97
  ## Documentation Drift
@@ -73,18 +103,18 @@ Set `doc_drift_summary = { skipped_entirely: true }` and auto-proceed.
73
103
 
74
104
  ### 3. Build Drift Findings
75
105
 
76
- Categorize results:
77
- - **changed:** entries where `content_hash` differs from newly computed hash
78
- - **unchanged:** entries where hashes match
79
- - **fetch_failed:** entries where the URL could not be reached
80
- - **skipped_null_hash:** entries where `content_hash` was `null`
106
+ Read the categories and totals directly from the script's JSON — no manual counting. The four buckets are already computed:
107
+ - **changed:** entries where the stored `content_hash` differs from the newly computed hash (`changed[]`)
108
+ - **unchanged:** entries where the hashes match (`unchanged[]`)
109
+ - **fetch_failed:** entries where the URL could not be reached (`fetch_failed[]`)
110
+ - **skipped_null_hash:** entries where `content_hash` was `null` (`skipped_null_hash[]`)
81
111
 
82
- Compute totals:
83
- - `total_tracked` = length of `doc_sources`
84
- - `changed` = count of drifted entries
85
- - `unchanged` = count of matching entries
86
- - `fetch_failed` = count of fetch failures
87
- - `skipped_null_hash` = count of null-hash entries
112
+ Take the totals straight from `stats` (do not recount):
113
+ - `total_tracked` = `stats.total_tracked`
114
+ - `changed` = `stats.changed`
115
+ - `unchanged` = `stats.unchanged`
116
+ - `fetch_failed` = `stats.fetch_failed`
117
+ - `skipped_null_hash` = `stats.skipped_null_hash`
88
118
 
89
119
  ### 4. Append to Drift Report
90
120
 
@@ -144,4 +174,4 @@ Load, read the full file, then execute {nextStepFile}.
144
174
 
145
175
  ## CRITICAL STEP COMPLETION NOTE
146
176
 
147
- ONLY WHEN the ## Documentation Drift section has been appended to {outputFile} and workflow context updated will you then load and read fully `{nextStepFile}` to begin final report generation.
177
+ Only when the ## Documentation Drift section has been appended to {outputFile} and workflow context updated do you then load and read fully `{nextStepFile}` to begin final report generation.
@@ -7,6 +7,9 @@ loadProvenanceProbeOrder:
7
7
  compareFileHashesProbeOrder:
8
8
  - '{project-root}/_bmad/skf/shared/scripts/skf-compare-file-hashes.py'
9
9
  - '{project-root}/src/shared/scripts/skf-compare-file-hashes.py'
10
+ structuralDiffProbeOrder:
11
+ - '{project-root}/_bmad/skf/shared/scripts/skf-structural-diff.py'
12
+ - '{project-root}/src/shared/scripts/skf-structural-diff.py'
10
13
  ---
11
14
 
12
15
  <!-- Config: communicate in {communication_language}. -->
@@ -25,69 +28,57 @@ Compare the original provenance map extractions from create-skill against the cu
25
28
 
26
29
  ## MANDATORY SEQUENCE
27
30
 
28
- ### 1. Prepare Comparison Sets
31
+ ### 1. Run the Deterministic Export Diff
29
32
 
30
- Load both datasets:
33
+ The export comparison — canonicalization, set arithmetic (added/removed/moved), and field-level change detection — is fully deterministic and runs in one subprocess. Do **not** diff the two export lists by hand: an LLM comparing dozens or hundreds of exports can silently drop or mis-match entries, which violates this skill's zero-hallucination contract.
31
34
 
32
- **Baseline (from provenance map):**
33
- - Export list with names, types, signatures, file paths, line numbers
35
+ **Resolve `{structuralDiffHelper}`** from `{structuralDiffProbeOrder}`; first existing path wins. HALT if no candidate exists.
34
36
 
35
- **Current (from Step 02 extraction):**
36
- - Export list with names, types, signatures, file paths, line numbers
37
+ Run one comparison over the baseline provenance map (from step 1) and the current extraction snapshot (`{extractionSnapshot}`, written to disk by step 2 §3):
37
38
 
38
- **Canonicalize extractor methodology differences before matching.** The extractor used by `skf-create-skill` at baseline time and the re-extractor used by step 2 can differ in cosmetic detail (quote style, module qualification, re-export resolution). Without normalization, those cosmetic differences surface as false-positive "Changed" and "Removed" entries even when the source commit has not moved. Apply these transforms to both sets symmetrically:
39
-
40
- - **Quote style on string defaults.** Normalize string-literal defaults in signatures to a single style — e.g., `kind: str = "Hnsw"` ↔ `kind: str = 'Hnsw'`. Pick one canonical form and apply to both sides.
41
- - **Module qualification of stdlib helpers.** Strip module prefixes on well-known stdlib helpers when the unqualified form is importable at the call site: `dataclasses.field(...)` → `field(...)`, `typing.Optional[...]` → `Optional[...]`, `typing.List[...]` → `List[...]`. Do not collapse user-defined namespaces.
42
- - **Public-API re-export resolution.** When `{source_root}/**/__init__.py` re-exports an internal symbol under a different public name (`from .internal import _Impl as Public`, or via `__all__`), resolve both sides to the public name before key-matching — otherwise a renamed re-export in the current scan shows up as "Removed `_Impl`" + "Added `Public`" instead of matching the baseline entry. The re-export map is already in workflow context as `{reexport_map}` (produced by `skf-load-provenance.py normalize` in step 1 §4). Apply it directly without re-walking `__init__.py` files.
43
-
44
- Record the set of transforms actually applied in workflow context — step 6 surfaces them in the Provenance section so a reviewer can tell which differences the diff collapsed and which were real.
45
-
46
- Normalize both sets for comparison:
47
- - Match by canonicalized export name (primary key)
48
- - Group by file for location-aware comparison
49
-
50
- > **Longer-term fix.** The principled remedy is to persist `skf-create-skill`'s ast-grep ruleset to `{forge_version}/extraction-rules.yaml` at create time and have step 2 replay that exact ruleset. When the file is present, step 2 extraction becomes reproducible against the baseline and the canonicalization pass above becomes a no-op. Until then, normalization is the salvage remediation for provenance maps that predate extractor pinning.
39
+ ```bash
40
+ uv run {structuralDiffHelper} {provenanceMap} {extractionSnapshot}
41
+ ```
51
42
 
52
- ### 2. Detect Added Exports
43
+ The helper reads both shapes directly — the provenance map's `entries[]` (with `export_name`/`export_type`/`source_file`/`source_line`) and the snapshot's `exports[]` — and aliases the field names, so no manual projection is needed.
53
44
 
54
- **Launch subprocess (Pattern 4 parallel execution):** In Claude Code, use multiple parallel Agent tool calls. In CLI, use `xargs -P` or equivalent.
45
+ **Canonicalization is applied inside the helper, symmetrically to both sides**, before name-keyed matching so cosmetic extractor differences do not surface as false-positive "Changed"/"Removed"/"Added" entries:
55
46
 
56
- Find exports that exist in current scan but NOT in provenance map.
47
+ - **Quote style on string defaults** `kind: str = "Hnsw"` ↔ `kind: str = 'Hnsw'`.
48
+ - **Stdlib module qualification** — `typing.Optional[...]` → `Optional[...]`, `dataclasses.field(...)` → `field(...)` (user-defined namespaces are never collapsed).
49
+ - **Public-API re-export resolution** — a renamed public re-export (`_Impl` → `Public`) matches the baseline entry instead of splitting into "Removed `_Impl`" + "Added `Public`". The re-export map is **auto-derived from the provenance map** (identical to the `{reexport_map}` projection `skf-load-provenance.py normalize` produced in step 1 §4). Pass `--reexport-map {file}` only to override with a custom map.
57
50
 
58
- For each added export, record:
59
- - Export name, type, signature
60
- - File path and line number (from current scan)
61
- - Confidence tier (T1 if AST-backed, T1-low if text-based)
51
+ Parse the emitted JSON:
62
52
 
63
- **If subprocess unavailable:** Iterate current exports, check against provenance map set.
53
+ ```
54
+ {
55
+ "summary": {"added": N, "removed": N, "changed": N, "moved": N, "unchanged": N},
56
+ "added": [ <entry>, ... ], // in current snapshot, NOT in provenance map
57
+ "removed": [ <entry>, ... ], // in provenance map, NOT in current snapshot
58
+ "changed": [ {"name", "field", "baseline_value", "current_value"}, ... ],
59
+ "moved": [ {"name", "previous_file", "current_file"}, ... ],
60
+ "unchanged_count": N,
61
+ "applied_transforms": [ {"transform": "quote-style|stdlib-prefix|reexport-resolution", "count": N}, ... ]
62
+ }
63
+ ```
64
64
 
65
- ### 3. Detect Removed Exports
65
+ Stash `applied_transforms` in workflow context — step 6 surfaces it in the Provenance section so a reviewer can tell which cosmetic differences the diff collapsed and which changes were real.
66
66
 
67
- Find exports that exist in provenance map but NOT in current scan.
67
+ **If `uv` / the helper cannot execute** (e.g. claude.ai web): fall back to comparing the two lists by hand — match by canonicalized export name (apply the three transforms above to both sides), then read off added (current-only), removed (baseline-only), moved (same name, different `file`), and changed (matched name, differing type/signature/line). Compare a field only when it is present on both sides.
68
68
 
69
- For each removed export, record:
70
- - Export name, type, signature (from provenance map)
71
- - Original file path and line number
72
- - Confidence tier (T1 if AST-backed, T1-low if text-based)
69
+ ### 2. Read Added / Removed / Moved from the Diff
73
70
 
74
- **Special check:** If export name exists but in a different file, classify as MOVED (not removed).
71
+ These sets come straight from the helper's JSON no further set arithmetic:
75
72
 
76
- ### 4. Detect Changed Exports
73
+ - **Added** — `added[]`: exports in the current snapshot but not the provenance map. Each carries name, type, signature, file, line, confidence.
74
+ - **Removed** — `removed[]`: exports in the provenance map but not the current snapshot. Same fields (in provenance-map field names).
75
+ - **Moved** — `moved[]`: matched exports whose file path changed (`previous_file` → `current_file`). A move is **not** a removal.
77
76
 
78
- Find exports that exist in BOTH sets but have differences.
77
+ Confidence tier for each entry is the `confidence` field the extractor recorded (T1 if AST-backed, T1-low if text-based).
79
78
 
80
- Compare:
81
- - **Signature changes:** Parameter count, parameter types, return type
82
- - **Type changes:** Function became class, const became function, etc.
83
- - **Location changes:** Same name/signature but different file or line number (MOVED)
79
+ ### 3. Read Changed Exports from the Diff
84
80
 
85
- For each changed export, record:
86
- - Export name
87
- - Original signature → Current signature
88
- - Original location → Current location
89
- - What changed (signature / type / location)
90
- - Confidence tier
81
+ `changed[]` lists per-field differences for exports present in BOTH sets. Each item names the export, the `field` that changed (type / signature / line / confidence), and its `baseline_value` → `current_value`. Group items by export name when compiling the report, and pair with the export's `moved[]` entry (if any) to describe location changes.
91
82
 
92
83
  ### 4b. Detect Script/Asset Drift
93
84
 
@@ -114,7 +105,9 @@ Parse the emitted JSON:
114
105
 
115
106
  Hash-prefix normalization (writer-vs-reader compatibility — `skf-create-skill` writes `content_hash` with a `"sha256:"` prefix, a bare-hex hash from `hashlib` would otherwise never match) is handled inside the script. Downstream consumers read `added`/`removed`/`changed` directly with no further normalization.
116
107
 
117
- Append the three lists into the Structural Drift section as "### Script/Asset Drift ({stats.added + stats.removed + stats.changed})".
108
+ Append the three lists into the Structural Drift section under a `### Script/Asset Drift (added {stats.added}, removed {stats.removed}, changed {stats.changed})` heading — take each count straight from `stats`, no recount.
109
+
110
+ **If `uv`/the helper cannot execute** (e.g. claude.ai web): skip the script/asset drift check with a `### Script/Asset Drift — skipped (hashing helper unavailable)` note rather than blocking the audit. This check is supplementary to the export diff, which has its own by-hand fallback in §1.
118
111
 
119
112
  ### Stack-Specific Structural Diff
120
113
 
@@ -122,7 +115,7 @@ If `{is_stack_skill}` is true:
122
115
 
123
116
  **For v2 provenance (per-export entries with `source_library`):**
124
117
  - Group entries by `source_library`
125
- - For each library, perform the standard structural diff (same as single-skill) against current source
118
+ - For each library, run the same deterministic diff as the single-skill path (§1) pass the per-library baseline slice and the matching current snapshot to `{structuralDiffHelper}`
126
119
  - Report per-library diff results
127
120
 
128
121
  **For code-mode stacks:** Re-extract from each source repo and compare per-library entries.
@@ -135,7 +128,7 @@ If `{is_stack_skill}` is true:
135
128
 
136
129
  ### 5. Compile Structural Drift Section
137
130
 
138
- **Rollup for high-volume uniform findings.** When ≥ 10 findings in the same table share one root cause (deleted source file, renamed module, entire package tree removed), you MAY collapse them into one row per root cause. Rollup rows replace the per-symbol `Export`/`Signature` columns with `Count` and `Representative symbols` (up to 3 names, `…` if more). Rollup applies to **Added Exports**, **Removed Exports**, and **Script/Asset Drift** tables — **not** to Changed Exports, which are heterogeneous by construction (signature changes and cross-file changes are inspected per-finding). Record which groupings were collapsed in workflow context for reviewer traceability.
131
+ **Rollup for high-volume uniform findings.** When ≥ 10 findings in the same table share one root cause (deleted source file, renamed module, entire package tree removed), you may collapse them into one row per root cause. Rollup rows replace the per-symbol `Export`/`Signature` columns with `Count` and `Representative symbols` (up to 3 names, `…` if more). Rollup applies to **Added Exports**, **Removed Exports**, and **Script/Asset Drift** tables — **not** to Changed Exports, which are heterogeneous by construction (signature changes and cross-file changes are inspected per-finding). Record which groupings were collapsed in workflow context for reviewer traceability.
139
132
 
140
133
  **Rollup row form (Added / Removed Exports):**
141
134
 
@@ -181,23 +174,5 @@ Append to {outputFile}:
181
174
 
182
175
  ### 6. Update Report and Auto-Proceed
183
176
 
184
- Update {outputFile} frontmatter:
185
- - Append `'structural-diff'` to `stepsCompleted`
186
-
187
- ### 7. Present MENU OPTIONS
188
-
189
- Display: "**Structural diff complete. {total} drift items found. Proceeding to semantic diff...**"
190
-
191
- #### Menu Handling Logic:
192
-
193
- - After structural diff section is appended and frontmatter updated, immediately load, read entire file, then execute {nextStepFile}
194
-
195
- #### EXECUTION RULES:
196
-
197
- - This is an auto-proceed analysis step with no user choices
198
- - Proceed directly to next step after completion
199
-
200
- ## CRITICAL STEP COMPLETION NOTE
201
-
202
- ONLY WHEN the ## Structural Drift section has been appended to {outputFile} with all findings documented will you then load and read fully `{nextStepFile}` to execute and begin semantic diff analysis.
177
+ Update {outputFile} frontmatter — append `'structural-diff'` to `stepsCompleted`. Once the ## Structural Drift section has been appended, load, read fully, and execute `{nextStepFile}` (semantic diff).
203
178
 
@@ -9,11 +9,9 @@ description: Design a skill scope through guided discovery. Use when the user re
9
9
 
10
10
  Helps the user define what to skill — target repo, scope, language, inclusion/exclusion patterns — and produces a `skill-brief.yaml` that drives create-skill. This is the first step in the skill creation pipeline; the brief is the input contract for create-skill, which performs the actual compilation.
11
11
 
12
- A good skill brief sets a tight, cohesive boundary: one capability with 3-8 primary functions, an unambiguous public API surface, and a description short enough to fit in a registry row. Briefs that try to cover several unrelated concerns (e.g. authentication *and* data visualization) compile into skills that no agent can route to confidently — a brief covering too much is a worse failure mode than a brief covering too little, and this workflow steers toward the smaller, sharper version when scope is unclear.
12
+ A good skill brief sets a tight, cohesive boundary: one capability with 3-8 primary functions, an unambiguous public API surface, and a description short enough to fit in a registry row. Briefs that try to cover several unrelated concerns (e.g. authentication *and* data visualization) compile into skills that no agent can route to confidently — a brief covering too much is a worse failure mode than a brief covering too little, and this workflow steers toward the smaller, sharper version when scope is unclear. Scope on cheap signals — manifests, top-level exports, intent — not full AST extraction.
13
13
 
14
- Brief-skill is split from create-skill so the scoping conversation runs *once*, on cheap signals (manifests, top-level exports, intent), without paying for AST extraction. Compilation is expensive; scoping decisions are cheap to revise. Keeping them in separate workflows lets a user iterate on the brief, share it for review, and re-run create-skill against the same brief whenever the upstream version moves.
15
-
16
- **Ratify path.** When the user already has a `skill-brief.yaml` produced by another workflow — typically `skf-analyze-source`'s `generate-briefs` step, where one analyze pass emits several recommended briefs — the brief can be *ratified* (reviewed and rewritten without re-deriving its fields) instead of authored from scratch. **Interactively:** invoke `/skf-brief-skill` with a path to that brief at the first prompt — gather-intent §3.1a loads the YAML, hydrates the brief context, and jumps straight to step 4 (confirm-brief) where the standard review/edit cycle still applies before step 5 writes. **Headlessly:** pass `from_brief <path>` — the step 1 §8 GATE runs the same schema validation and hydration and writes through the canonical writer (overwriting in place), enabling a fully headless analyze → brief → create pipeline that preserves the upstream-authored scope. Either path skips re-deriving fields the upstream draft already supplies and saves the 5-10 minutes a full re-run of intent + analyze + scope would cost.
14
+ **Ratify path.** A pre-authored `skill-brief.yaml` (typically from `skf-analyze-source`'s `generate-briefs` step) can be *ratified* reviewed and rewritten in place instead of re-derived from scratch. Interactively, pass its path at the first prompt; headlessly, pass `from_brief <path>`. See the `from_brief` Inputs cell in `references/invocation-contract.md` for the full ratify contract.
17
15
 
18
16
  ## Conventions
19
17
 
@@ -63,8 +61,11 @@ These rules apply to every step in this workflow:
63
61
  - `{descriptionVoiceExamplesPath}` ← `workflow.description_voice_examples_path` if non-empty, else `assets/description-voice-examples.md`
64
62
  - `{scopeTemplatesPath}` ← `workflow.scope_templates_path` if non-empty, else `assets/scope-templates.md`
65
63
  - `{briefSchemaPath}` ← `workflow.brief_schema_path` if non-empty, else `assets/skill-brief-schema.md`
64
+ - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — write-brief.md skips the hook invocation entirely)
65
+
66
+ Stash all four as workflow-context variables. Stage files reference `{descriptionVoiceExamplesPath}` / `{scopeTemplatesPath}` / `{briefSchemaPath}` / `{onCompleteCommand}` 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 (or wire in a pipeline hook) without forking the skill.
66
67
 
67
- Stash all three as workflow-context variables. Stage files reference `{descriptionVoiceExamplesPath}` / `{scopeTemplatesPath}` / `{briefSchemaPath}` 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.
68
+ Also apply the array surfaces so they are not silent no-ops: execute each entry in `workflow.activation_steps_prepend` in order now; treat every entry in `workflow.persistent_facts` as standing context for the whole run (`file:`-prefixed entries are paths or globs whose contents load as facts — the bundled default loads any `project-context.md` under `{project-root}`); then, after activation completes and before step 4 loads the first stage, execute each entry in `workflow.activation_steps_append` in order.
68
69
 
69
70
  4. Load, read the full file, and execute `references/gather-intent.md`.
70
71
 
@@ -85,34 +86,4 @@ Stages 1a-1b are conditional — they replace stages 2-5 when BS is invoked with
85
86
 
86
87
  ## Invocation Contract
87
88
 
88
- | Aspect | Detail |
89
- |--------|--------|
90
- | **Inputs** | `target_repo` [required], `skill_name` [required], `scope_hint` [optional], `language_hint` [optional], `target_version` [optional], `source_authority` [optional: official/community/internal, default community], `source_type` [optional: source/docs-only, default source], `doc_urls` [optional: list of `url[,label]` for source_type=docs-only or supplemental], `scope_type` [optional: full-library/specific-modules/public-api/component-library/reference-app/docs-only], `include` [optional: comma-separated globs], `exclude` [optional: comma-separated globs], `scripts_intent` [optional: detect/none/free-text, default detect], `assets_intent` [optional: detect/none/free-text, default detect], `intent` [optional: free-text used to derive description], `force` [optional: overwrite existing brief without prompting], `from_brief` [optional: path to a pre-authored `skill-brief.yaml` to *ratify* — when supplied it is the source of truth, `target_repo`/`skill_name` become optional/derived-from-brief, and the run mirrors the interactive §3.1a ratify path: schema-validate, skip analyze-target/scope-definition, write through the canonical writer in place], `[auto]` [optional: bracket modifier passed via pipeline context — when present, BS loads the upstream brief from `brief_path` in pipeline data, enriches it with doc detection, and writes through the canonical writer; requires `brief_path` from AN's `SKF_ANALYZE_RESULT_JSON`] |
91
- | **Gates** | step 1: Input Gate [use args] | step 3: Confirm Gate [C] | step 4: Confirm Gate [C] |
92
- | **Outputs** | `skill-brief.yaml` at `{forge_data_folder}/{skill-name}/skill-brief.yaml`; final `SKF_BRIEF_RESULT_JSON` line on stdout when `{headless_mode}` is true |
93
- | **Headless** | All gates auto-resolve with heuristic-driven or default action when `{headless_mode}` is true; pre-supplied inputs consumed at the gates that would otherwise prompt; absent `source_authority` and `scope_type` are resolved by signal-driven detection (see `references/headless-args.md`); existing briefs are preserved unless `--force` was supplied (HALT with `overwrite-cancelled` otherwise); supplying `from_brief <path>` instead routes the step 1 GATE to a ratify path that schema-validates the pre-authored brief, skips analyze-target/scope-definition, and writes through the canonical writer (overwriting in place, no `--force` needed) rather than deriving a new brief |
94
- | **Transient-failure retry** | This workflow does **not** auto-retry network or subprocess failures. A failed `gh` fetch (analyze-target.md §1, portfolio-similarity-check.md), QMD probe, or extraction script is logged and surfaced in the final result envelope as a warning, but the workflow continues with whatever signal it has. Headless pipelines that want retry semantics should wrap the invocation at their orchestrator level (e.g. CI re-runner on non-zero exit). Rationale: brief-skill is read-mostly with one terminal write (the YAML at step 5); a partial-signal retry has more failure modes than just re-running the whole workflow, which is cheap. |
95
- | **Exit codes** | See "Exit Codes" below |
96
-
97
- ## Exit Codes
98
-
99
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
100
-
101
- | Code | Meaning | Raised by |
102
- | ---- | -------------------- | ------------------------------------------------------------------------------------------ |
103
- | 0 | success | step 6 (terminal) |
104
- | 2 | input-missing / input-invalid | step 1 GATE — required headless arg absent (`target_repo`, `skill_name`, or `doc_urls` when `source_type=docs-only`) → `input-missing`; enum violation, malformed semver, non-kebab `skill_name`, or step 5 brief-context schema validation failure → `input-invalid` |
105
- | 3 | resolution-failure | step 1 §1 (`forge-tier.yaml` missing); step 2 §1 (target inaccessible / `gh auth` fails) |
106
- | 4 | write-failure | step 1 §1 pre-flight write probe (data folder unwritable: read-only mount, disk full, permissions denied); step 5 §4 (write to `{forge_data_folder}/{skill-name}/skill-brief.yaml` failed) |
107
- | 5 | overwrite-cancelled | step 5 §2 (existing brief, `force` not supplied) |
108
- | 6 | user-cancelled | any interactive menu in step 1/03/04 (user selected `[X]` Cancel and exit) |
109
-
110
- ## Result Contract (Headless)
111
-
112
- When `{headless_mode}` is true, step 5 emits a single-line JSON envelope on **stdout** before chaining to step 6, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
113
-
114
- ```
115
- SKF_BRIEF_RESULT_JSON: {"status":"success|error","brief_path":"…|null","skill_name":"…","version":"…|null","language":"…|null","scope_type":"…|null","exit_code":0,"halt_reason":null,"mode":"auto|null"}
116
- ```
117
-
118
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"forge-tier-missing"`, `"target-inaccessible"`, `"gh-auth-failed"`, `"write-failed"`, `"overwrite-cancelled"`, `"user-cancelled"`. `exit_code` matches the table above. `mode` is `"auto"` when BS was invoked with the `[auto]` flag (pipeline auto-brief generation), `null` otherwise (interactive or headless-without-auto).
89
+ Headless callers: the full argument set (`Inputs`), gate map, exit-code table, and `SKF_BRIEF_RESULT_JSON` result envelope live in `references/invocation-contract.md`. Interactive runs do not need it.
@@ -121,7 +121,7 @@ Auto-detect demo directories and file patterns:
121
121
  - Directories: `demo/`, `demos/`, `stories/`, `examples/`, `__stories__/`, `storybook/`
122
122
  - Files: `*.stories.*`, `*.story.*`, `*.example.*`, `*.demo.*`
123
123
 
124
- Present detected patterns to user before applying (do NOT exclude silently).
124
+ Show detected patterns to the user for confirmation before applying them, rather than excluding files silently.
125
125
  Prompt: "**Auto-detected {N} demo/example files** in {M} directories. Confirm exclusion? [Y/n] Or adjust patterns:"
126
126
 
127
127
  **Phase 3 — Variant Selection (if applicable):**
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-brief-skill.
4
+ # Team overrides: _bmad/custom/skf-brief-skill.toml (under {project-root})
5
+ # Personal overrides: _bmad/custom/skf-brief-skill.user.toml (under {project-root})
4
6
 
5
7
  [workflow]
6
8
 
@@ -42,3 +44,15 @@ persistent_facts = [
42
44
  description_voice_examples_path = ""
43
45
  scope_templates_path = ""
44
46
  brief_schema_path = ""
47
+
48
+ # Pipeline-integration hook invoked once the brief is written and the result
49
+ # contract is finalized (write-brief.md, step 5), before the workflow chains to
50
+ # the terminal health-check step. The command is called as:
51
+ # <on_complete> --result-path=<absolute_path_to_skill-brief.yaml>
52
+ # Useful for chaining into create-skill, Slack notifications, dashboard ingest,
53
+ # or CI hooks. Override wins. Failures are logged to workflow_warnings[] but
54
+ # never fail the workflow.
55
+ #
56
+ # Empty string = no-op (default).
57
+
58
+ on_complete = ""