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,5 +1,13 @@
1
1
  ---
2
2
  nextStepFile: 'write.md'
3
+ # Resolve `{hashContentHelper}` to the first existing path; HALT if neither
4
+ # candidate exists. Check B uses its `manual-verify` subcommand to verify the
5
+ # merged (on-disk) SKILL.md against the byte-exact [MANUAL] inventory captured
6
+ # in step 1 §5 — the deterministic replacement for the LLM byte-identity
7
+ # eyeball, which could silently pass a subtly truncated block.
8
+ hashContentProbeOrder:
9
+ - '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
10
+ - '{project-root}/src/shared/scripts/skf-hash-content.py'
3
11
  ---
4
12
 
5
13
  <!-- Config: communicate in {communication_language}. -->
@@ -14,9 +22,9 @@ Validate the merged skill content against the agentskills.io specification, veri
14
22
 
15
23
  - Focus only on validation — do not fix issues (that's the user's choice)
16
24
  - Validation is read-only — do not modify merged content
17
- - Use parallel validation checks (Pattern 4) when available; if unavailable, check sequentially
25
+ - Run the two live structural checks (B, then C) in order; Checks A, D, E, F are deferred to step 6
18
26
 
19
- ## MANDATORY SEQUENCE
27
+ ## Steps
20
28
 
21
29
  ### 1. Check Tool Availability and Validation Timing
22
30
 
@@ -27,30 +35,40 @@ Run: `npx skill-check -h`
27
35
 
28
36
  **Important:** Do not assume availability — empirical check required.
29
37
 
30
- **Validation timing note:** Step-04 section 6b has already written SKILL.md (and stack reference files) to disk. External-tool checks against written files (skill-check Checks A, E, F) still run in **step 6 section 7** to co-locate external-tool validation with post-write verification. Structural checks (B, C, D) run here against the merged content — content on disk is byte-identical to the in-context copy.
38
+ **Validation timing note:** Step-04 section 6b has already written SKILL.md to disk. External-tool checks against written files (skill-check Checks A, E, F) still run in **step 6 section 7** to co-locate external-tool validation with post-write verification. Check D (Provenance Completeness) is deterministic but needs both `metadata.json` and `provenance-map.json` on disk — neither exists yet at this step — so it is deferred to **step 6 section 6a**, which runs it via the completeness helper against the freshly-written artifacts. Structural checks (B, C) run here against the merged content — content on disk is byte-identical to the in-context copy.
31
39
 
32
- ### 2. Launch Parallel Validation Checks
40
+ ### 2. Run Validation Checks
33
41
 
34
- Launch subprocesses in parallel for each validation category, aggregating results when complete:
42
+ Per the §1 timing note, only Check B (a single deterministic `manual-verify` script) and Check C (an in-prompt tier-label consistency check) run live at this step; Checks A, D, E, F are deferred to step 6. Run B then C in order — one script plus one in-prompt comparison have nothing to fan out.
35
43
 
36
44
  **Check A — Spec Compliance (deferred to post-write):**
37
45
 
38
46
  Skill-check requires written files on disk. This check is deferred to step 6 section 7. Perform manual structural check only: verify merged SKILL.md has required sections (exports, usage patterns, conventions), verify export entries have name/type/signature/file:line reference, flag missing sections.
39
47
 
40
48
  **Check B — [MANUAL] Section Integrity:**
41
- - Compare [MANUAL] inventory from step 01 against merged content
42
- - Verify every [MANUAL] block exists in merged result, byte-identical (zero modification)
43
- - Flag any [MANUAL] blocks moved, truncated, or missing
49
+
50
+ Run the deterministic [MANUAL]-integrity verifier against the step-1 §5 inventory — do not eyeball byte-identity (a subtly truncated block reads as intact to the eye):
51
+
52
+ ```bash
53
+ uv run {hashContentHelper} manual-verify {skill_package}/SKILL.md \
54
+ --inventory {manual_inventory}
55
+ ```
56
+
57
+ Read the verdict JSON `{"preserved":[...], "modified":[...], "missing":[...], "moved":[...], "ok":bool}` (the on-disk SKILL.md is byte-identical to the merged in-context copy per the timing note above):
58
+ - `ok == true` → status PASS. `preserved` blocks are byte-identical in place; any `moved` blocks are byte-identical but relocated with their logical parent section (report as informational, not a finding).
59
+ - `modified` non-empty → FAIL: these blocks' byte-exact interiors changed (the truncation a marker-count/eyeball check would miss). List each by name.
60
+ - `missing` non-empty → FAIL: these blocks lost their markers entirely. List each by name.
61
+
62
+ Populate the §3 `manual_integrity` record from this verdict (`sections_verified` = inventory count, `sections_intact` = `len(preserved) + len(moved)`). This check is advisory here (findings inform, do not block); write.md §1 enforces the same `ok` verdict as a HALT gate before any derived artifact is written.
44
63
 
45
64
  **Check C — Confidence Tier Consistency:**
46
65
  - Verify all re-extracted exports have confidence labels (T1/T1-low/T2)
47
66
  - Verify tier labels match forge tier: Quick=T1-low only, Forge=T1 (T1-low for degraded), Forge+=T1 (same as Forge, CCC improves coverage not confidence), Deep=T1+T2
48
67
  - Flag mismatched or missing tier labels
49
68
 
50
- **Check D — Provenance Completeness:**
51
- - Verify every export has a provenance map entry with valid file:line references
52
- - Verify no stale references to old file paths or line numbers
53
- - Flag orphaned provenance entries (export removed but provenance remains)
69
+ **Check D — Provenance Completeness (deferred to post-write):**
70
+
71
+ The provenance map does not exist on disk until step 6 §3 writes it, and completeness is a deterministic set-diff (metadata `exports[]` vs provenance `entries[].export_name`) plus file:line citation resolution — not something to eyeball. This check is deferred to **step 6 section 6a**, which runs `skf-verify-provenance-completeness.py` against the just-written `metadata.json` and `provenance-map.json` and reads back `missing[]` / `orphaned[]` / `stale[]` findings. Do not attempt the set comparison here — there is no provenance map to compare against yet. The `Provenance` row in §5's summary is populated from §6a's result.
54
72
 
55
73
  **Check E — Diff Comparison (via skill-check):**
56
74
 
@@ -83,16 +101,6 @@ Validation Results:
83
101
  quality_score: [0-100] # from skill-check, if available
84
102
  ```
85
103
 
86
- ### 4. For Stack Skills — Validate Reference Files
87
-
88
- **ONLY if skill_type == "stack":**
89
-
90
- Repeat checks A-D for each reference file:
91
- - `references/{library}.md` — spec compliance and [MANUAL] integrity
92
- - `references/integrations/{pair}.md` — spec compliance and [MANUAL] integrity
93
-
94
- **If skill_type != "stack":** Skip with notice.
95
-
96
104
  ### 5. Display Validation Summary
97
105
 
98
106
  "**Validation Results:**
@@ -110,13 +118,7 @@ Repeat checks A-D for each reference file:
110
118
 
111
119
  **If findings exist:** List each with severity, description, and location. Add: "Validation is advisory. Findings do not block the update."
112
120
 
113
- ### 6. Present MENU OPTIONS
114
-
115
- Display: "**Proceeding to write updated files...**"
116
-
117
- After validation summary is displayed, immediately load, read entire file, then execute {nextStepFile}. This is an auto-proceed step — validation is advisory, not blocking.
118
-
119
- ## CRITICAL STEP COMPLETION NOTE
121
+ ### 6. Route to Next Step
120
122
 
121
- ONLY WHEN all validation checks have completed and findings are displayed will you load {nextStepFile} to write the updated files. Validation does NOT block — it informs.
123
+ This step auto-proceeds — no user choices; validation is advisory and does not block, it only informs. Once all validation checks have completed and findings are displayed, display "**Proceeding to write updated files...**", then load, fully read, and execute `{nextStepFile}` to write the updated files.
122
124
 
@@ -17,6 +17,31 @@ descriptionGuardProbeOrder:
17
17
  updateActiveSymlinkProbeOrder:
18
18
  - '{project-root}/_bmad/skf/shared/scripts/skf-update-active-symlink.py'
19
19
  - '{project-root}/src/shared/scripts/skf-update-active-symlink.py'
20
+ # Resolve `{verifyProvenanceCompletenessHelper}` to the first existing path.
21
+ # §6a runs Check D (Provenance Completeness), deferred from step 5 because it
22
+ # needs both metadata.json + provenance-map.json on disk: the export/provenance
23
+ # set-diff (missing + orphaned entries) and file:line citation resolution.
24
+ # Advisory — do not HALT if neither path resolves; fall back to the in-prose
25
+ # set comparison §6a documents (graceful degradation).
26
+ verifyProvenanceCompletenessProbeOrder:
27
+ - '{project-root}/_bmad/skf/shared/scripts/skf-verify-provenance-completeness.py'
28
+ - '{project-root}/src/shared/scripts/skf-verify-provenance-completeness.py'
29
+ # Resolve `{hashContentHelper}` to the first existing path; HALT if neither
30
+ # candidate exists. §1 uses its `manual-verify` subcommand to verify the
31
+ # post-merge file against the byte-exact [MANUAL] inventory captured in step 1
32
+ # §5; a marker-count comparison would pass a block whose interior was truncated
33
+ # without changing the marker count.
34
+ hashContentProbeOrder:
35
+ - '{project-root}/_bmad/skf/shared/scripts/skf-hash-content.py'
36
+ - '{project-root}/src/shared/scripts/skf-hash-content.py'
37
+ # Resolve `{renderMetadataStatsHelper}` by probing `{renderMetadataStatsProbeOrder}`
38
+ # in order (installed SKF module path first, src/ dev-checkout fallback); first
39
+ # existing path wins. HALT if neither resolves — §2's `stats` block and
40
+ # `confidence_distribution` are computed values that must not be hand-binned,
41
+ # and this is the same helper sibling create-skill compiles them with.
42
+ renderMetadataStatsProbeOrder:
43
+ - '{project-root}/_bmad/skf/shared/scripts/skf-render-metadata-stats.py'
44
+ - '{project-root}/src/shared/scripts/skf-render-metadata-stats.py'
20
45
  ---
21
46
 
22
47
  <!-- Config: communicate in {communication_language}. -->
@@ -25,7 +50,7 @@ updateActiveSymlinkProbeOrder:
25
50
 
26
51
  ## STEP GOAL:
27
52
 
28
- Verify the merged SKILL.md and stack reference files that step 4 section 6b wrote to disk, then write the derived artifacts (metadata.json, provenance-map.json, evidence-report.md, context-snippet.md, and the active symlink).
53
+ Verify the merged SKILL.md that step 4 section 6b wrote to disk, then write the derived artifacts (metadata.json, provenance-map.json, evidence-report.md, context-snippet.md, and the active symlink).
29
54
 
30
55
  ## Rules
31
56
 
@@ -34,7 +59,7 @@ Verify the merged SKILL.md and stack reference files that step 4 section 6b wrot
34
59
  - Do not skip provenance map update — critical for future audits
35
60
  - HALT immediately on verification failure before writing any derived artifact — a partial-write skill package is worse than an unchanged one
36
61
 
37
- ## MANDATORY SEQUENCE
62
+ ## Steps
38
63
 
39
64
  ### 0. Description Guard Protocol
40
65
 
@@ -48,13 +73,19 @@ Update-skill does not run the optional post-restore frontmatter re-validation to
48
73
 
49
74
  SKILL.md was written in step 4 section 6b. Verify the write landed intact before proceeding to any derived-artifact writes.
50
75
 
51
- - Read `{skill_package}/SKILL.md` from disk
52
- - Count `<!-- [MANUAL:*] -->` opening markers and compare against the [MANUAL] inventory captured in step 1
53
76
  - Verify the resolved `{skill_package}` path matches the version directory step 4 wrote to (if the version changed, step 4 updated `{skill_package}` in context to point at the new path)
54
- - If [MANUAL] count matches and path resolves: proceed to section 2
55
- - **If [MANUAL] count does not match: HALT immediately.** Do not write `metadata.json`, `provenance-map.json`, or any other artifact — further writes would compound the inconsistency. Alert the user:
77
+ - Run the deterministic [MANUAL]-integrity verifier against the byte-exact inventory captured in step 1 §5:
56
78
 
57
- "**[MANUAL] section count mismatch after write.** Expected {N} from step 1 inventory, found {M} on disk at `{skill_package}/SKILL.md`. The skill package is in an inconsistent state. Manual recovery required — restore the previous version from `{skill_group}/{previous_version}/` or fix the file in place, then re-run update-skill."
79
+ ```bash
80
+ uv run {hashContentHelper} manual-verify {skill_package}/SKILL.md \
81
+ --inventory {manual_inventory}
82
+ ```
83
+
84
+ The verdict JSON is `{"preserved":[...], "modified":[...], "missing":[...], "moved":[...], "ok":bool}`. A `modified` block is one whose byte-exact interior changed (an interior truncation); a `missing` block lost its markers entirely; a `moved` block is byte-identical but relocated with its logical parent section (clean — does not fail the gate). `ok == (modified empty AND missing empty)`.
85
+ - If `ok == true` and the path resolves: proceed to section 2
86
+ - **If `ok == false`: HALT immediately** with status `halted-for-manual-mismatch`. Do not write `metadata.json`, `provenance-map.json`, or any other artifact — further writes would compound the inconsistency. In `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-manual-integrity", path: "{skill_package}/SKILL.md", reason: "..."}`). Alert the user:
87
+
88
+ "**[MANUAL] section integrity failure after write.** Blocks modified (interior changed): {modified}. Blocks missing (markers lost): {missing}. Relocated-but-intact (advisory only): {moved}. Verified against the step-1 inventory `{manual_inventory}`, on disk at `{skill_package}/SKILL.md`. The skill package is in an inconsistent state. Manual recovery required — restore the previous version from `{skill_group}/{previous_version}/` or fix the file in place, then re-run update-skill."
58
89
 
59
90
  ### 2. Write Updated metadata.json
60
91
 
@@ -64,22 +95,28 @@ Update `{skill_package}/metadata.json`:
64
95
  - Update `version`: **if `update_mode == "gap-driven"`, do not bump — the skill is being repaired against the same source commit, so leave `version` unchanged and update only `generation_date` / `last_update` below.** This keeps metadata `version` consistent with the on-disk `{skill_package}` path, which step 4 §6b also leaves unchanged in gap-driven mode (see step 4 §6b's "If the source version detected during step 3 differs..." carve-out — in gap-driven mode no source version is detected, so step 4 writes into the existing version directory). Otherwise, if a source version was detected during re-extraction and differs from the current metadata version, use the source version; otherwise increment patch version
65
96
  - Update `generation_date` timestamp to current ISO-8601 date
66
97
  - Update `exports` array to reflect current export list
67
- - Update `stats` from re-extraction results:
68
- - `exports_documented`: count of exports with documentation in the merged skill
98
+ - **Compute the `stats` block and `confidence_distribution` deterministically** with `{renderMetadataStatsHelper}` (resolve from `{renderMetadataStatsProbeOrder}`; first existing path wins) — the same helper sibling create-skill compiles them with, so create and update emit byte-identical stats for identical inputs. The helper owns all the arithmetic: it bins each provenance `entries[]` row once by its `signature_source` tier into `confidence_distribution.{t1,t1_low,t2,t3}`, sets `exports_documented` = the entry count, and derives `exports_total` = `exports_public_api` + `exports_internal`, `public_api_coverage` = documented / public_api (`null` if public_api is 0), `total_coverage` = documented / total (`null` if total is 0), plus `scripts_count` / `assets_count` from the inventory arrays. Run `uv run {renderMetadataStatsHelper} --help` for the full contract. Do not hand-bin the distribution — binning T2 annotations + T3 doc items on top of the per-export tiers double-counts, which per-entry binning by `signature_source` makes structurally impossible. You supply only the judgment payload:
99
+
100
+ **Judgment payload (what you decide — passed as JSON on stdin):**
69
101
  - `exports_public_api`: count of exports from public entry points (`__init__.py`, `index.ts`, `lib.rs`, or equivalent)
70
102
  - `exports_internal`: count of all other non-underscore-prefixed exports
71
- - `exports_total`: `exports_public_api` + `exports_internal`
72
- - `public_api_coverage`: `exports_documented / exports_public_api` (`null` if `exports_public_api` is 0)
73
- - `total_coverage`: `exports_documented / exports_total` (`null` if `exports_total` is 0)
74
- - Update `confidence_distribution` from re-extraction results:
75
- - `confidence_distribution.t1`, `confidence_distribution.t1_low`, `confidence_distribution.t2`, `confidence_distribution.t3`: update counts from re-extraction results
76
- - `scripts_count`, `assets_count`: update from re-extraction results if scripts/assets changed
77
- - For stack skills: update `library_count`, `integration_count` if changed
103
+ - `scripts` / `assets`: the scripts / assets inventory arrays (or `[]` when empty) — the helper sets `scripts_count` / `assets_count` from their lengths
104
+
105
+ **Invoke** since the helper reads `entries[]`, stage §3's `provenance-map.json` write first (§3 does not depend on these stats):
106
+
107
+ ```bash
108
+ echo '{"exports_public_api": {N}, "exports_internal": {M}, "scripts": {scripts-inventory-or-[]}, "assets": {assets-inventory-or-[]}}' \
109
+ | uv run {renderMetadataStatsHelper} {forge_version}/provenance-map.json
110
+ ```
111
+
112
+ Write the returned `stats` and `confidence_distribution` objects into `metadata.json` **verbatim**. If the helper reports `coherence.ok: false`, some provenance entries carry a missing/unrecognized `signature_source` (§3 must write it on every entry) — fix the provenance map, do not hand-edit the stats.
78
113
 
79
114
  ### 3. Write Updated provenance-map.json
80
115
 
81
116
  Write to `{forge_version}/provenance-map.json`:
82
117
 
118
+ **Every entry this step writes or rewrites carries a `signature_source` (`T1` / `T1-low` / `T2` / `T3`)** — the tier that contributed the structural signature, matching create-skill's entry contract. §2's stats helper bins each entry on this field, so a missing value trips its `coherence.ok: false` check. Preserve it byte-identical on untouched entries; set it from the contributing extraction tier on every re-extracted or new entry.
119
+
83
120
  **If `no_reextraction == true` (gap-driven mode from step 3 section 0):**
84
121
  Dispatch per-entry on the verification outcome recorded by step 3 — gap-driven runs produce a mix of `verified`, `moved`, `re-extracted`, and `unknown` outcomes, and each requires a different provenance-map write strategy:
85
122
 
@@ -122,6 +159,8 @@ Dispatch per-entry on the verification outcome recorded by step 3 — gap-driven
122
159
  }
123
160
  ```
124
161
 
162
+ `manual_sections_preserved` = `len(preserved) + len(moved)` from the §1 `manual-verify` verdict (blocks that survived byte-identical, whether in place or relocated). Do not re-count markers by hand — the §1 verdict is the deterministic source.
163
+
125
164
  ### 4. Write Updated evidence-report.md
126
165
 
127
166
  Append update operation section to `{forge_version}/evidence-report.md` (create the file with a standard header if it does not yet exist):
@@ -168,18 +207,9 @@ Append update operation section to `{forge_version}/evidence-report.md` (create
168
207
 
169
208
  **Context Snippet population** (used by §5 after the staleness check runs): §4 writes the sub-block with placeholders; §5 updates the on-disk evidence report in place after deciding whether to regenerate. Set `Regenerated: true` and populate `Triggers fired` with any combination of `headline-exports`, `version`, `gotchas` when at least one trigger fired. Set `Regenerated: false` and `Triggers fired: —` when none fired (the gap-driven / internals-only outcome). Always fill `Notes` with a one-sentence reason (e.g., `"Gap-driven repair — no snippet surface changed"`, `"Version bumped 0.1.0 → 0.2.0; headline exports re-ranked"`).
170
209
 
171
- ### 5. Verify Stack Skill Reference File Writes (Conditional) and Regenerate context-snippet.md
210
+ ### 5. Regenerate context-snippet.md
172
211
 
173
- **ONLY if skill_type == "stack":**
174
-
175
- Stack reference files were written in step 4 section 6b. Verify each affected reference file that the merge produced:
176
-
177
- - Read each `references/{library}.md` back from disk
178
- - Read each `references/integrations/{pair}.md` back from disk
179
- - Verify per-file [MANUAL] section counts match the per-file inventory captured in step 1
180
- - **If any verification fails: HALT** using the same recovery protocol as section 1 — do not regenerate `context-snippet.md` or write any further derived artifact
181
-
182
- **For all skills (both single and stack) — regenerate `context-snippet.md` if stale:**
212
+ **Regenerate `context-snippet.md` if stale:**
183
213
 
184
214
  `context-snippet.md` is a `{skill_package}` deliverable that goes stale whenever **headline exports**, **version**, or **gotchas** change in this run. Regenerate it only when at least one of these triggers fired; otherwise skip — a skip is the correct outcome for gap-driven repairs and other runs that touch internals below the snippet's surface, where regenerating would produce byte-identical content.
185
215
 
@@ -193,10 +223,7 @@ Stack reference files were written in step 4 section 6b. Verify each affected re
193
223
 
194
224
  **If no trigger fired:** skip regeneration — do not touch `context-snippet.md` on disk. The snippet remains valid against the prior run's surface. Continue to §5b.
195
225
 
196
- **If at least one trigger fired:** regenerate the snippet using the format from the matching template file:
197
-
198
- - For single skills: `skf-create-skill/assets/skill-sections.md` (pipe-delimited indexed format)
199
- - For stack skills: `skf-create-stack-skill/assets/stack-skill-template.md`
226
+ **If at least one trigger fired:** regenerate the snippet using the format from `skf-create-skill/assets/skill-sections.md` (pipe-delimited indexed format).
200
227
 
201
228
  Use the **flat draft form** for the `root:` path in the draft snippet: `root: skills/{skill-name}/`. The per-IDE skill root (e.g., `.claude/skills/`, `.windsurf/skills/`, `.github/skills/` — see `skf-export-skill/assets/managed-section-format.md`) is applied later by `export-skill` step 3 when the skill is exported. Do not choose an IDE-specific prefix in update-skill — that is an export-time decision that depends on config.yaml.
202
229
 
@@ -204,8 +231,6 @@ Pull values for the regenerated snippet from the updated metadata.json (version,
204
231
 
205
232
  Write the regenerated snippet to `{skill_package}/context-snippet.md`, preserving file permissions.
206
233
 
207
- **If skill_type == "stack"**, also verify that the reference file updates from the first half of this section have been applied before writing the snippet so the stack snippet reflects the newest integration list.
208
-
209
234
  ### 5b. Update Active Symlink
210
235
 
211
236
  Flip `{skill_group}/active` to point at the current `{version}` via the helper. The call is **always** run — atomic, idempotent, and verified in one shot. The helper's no-op path handles the "version did not change" case (gap-driven mode, or no source drift) without writing to disk:
@@ -225,9 +250,11 @@ The helper emits a result envelope with `status` ∈ `{ok, flipped, mismatch, mi
225
250
  - **`missing-target`** (exit 2): `{skill_group}/{version}/` directory does not exist on disk. HALT — display `halt_message` verbatim. This indicates §4 §6b did not write the version directory before §5b ran (a workflow bug, not a user error).
226
251
  - **`mismatch`** (exit 2): re-read after flip showed the symlink still points elsewhere. HALT — display `halt_message`. Should be impossible because the helper uses `os.replace` (atomic rename); a mismatch here indicates filesystem-level interference (concurrent writer, broken FUSE mount).
227
252
 
253
+ Both exit-2 halts carry status `halted-for-write-failure`; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:active-symlink", reason: "..."}`).
254
+
228
255
  ### 6. Verify Derived Artifact Writes
229
256
 
230
- SKILL.md was verified in section 1 and stack reference files in section 5 (both written by step 4 section 6b). This section verifies the artifacts this step wrote: `metadata.json`, `provenance-map.json`, `evidence-report.md`, `context-snippet.md`, and the `active` symlink from §5b.
257
+ SKILL.md was verified in section 1 (written by step 4 section 6b). This section verifies the artifacts this step wrote: `metadata.json`, `provenance-map.json`, `evidence-report.md`, `context-snippet.md`, and the `active` symlink from §5b.
231
258
 
232
259
  For each derived artifact:
233
260
  - Read back the file
@@ -246,12 +273,33 @@ For each derived artifact:
246
273
  | evidence-report.md | {VERIFIED/FAILED} |
247
274
  | context-snippet.md | {VERIFIED/FAILED} |
248
275
  | {skill_group}/active symlink | {VERIFIED/FAILED} (readlink → {resolved_version}, expected {version}) |
249
- | {stack reference files...} | {VERIFIED in section 5} |
250
276
 
251
- **On symlink `mismatch` (helper exit 2):** HALT. Do not proceed to §7 post-write validation or §8 menu. Display the helper's `halt_message` verbatim — it already includes the diverged target, the expected version, and the recovery command. This matches the severity of the other four artifact checks — silent divergence here mis-routes any downstream consumer that uses the symlink fallback.
277
+ **On symlink `mismatch` (helper exit 2):** HALT with status `halted-for-write-failure`. Do not proceed to §7 post-write validation or §8 menu. Display the helper's `halt_message` verbatim — it already includes the diverged target, the expected version, and the recovery command; in `{headless_mode}`, emit the halt envelope per SKILL.md §Headless (`error: {phase: "write:verify-active-symlink", reason: "..."}`). This matches the severity of the other four artifact checks — silent divergence here mis-routes any downstream consumer that uses the symlink fallback.
252
278
 
253
279
  **All files written and verified.**"
254
280
 
281
+ ### 6a. Provenance Completeness (Check D — Deferred from Step 05)
282
+
283
+ `validate.md` Check D (Provenance Completeness) is a deterministic set-diff plus citation resolution, and it needs both `metadata.json` (written in §2) and `provenance-map.json` (written in §3) on disk — neither exists at validate time, so the check is deferred here. Run it against the just-written artifacts via `{verifyProvenanceCompletenessHelper}`:
284
+
285
+ ```bash
286
+ uv run {verifyProvenanceCompletenessHelper} verify \
287
+ --metadata {skill_package}/metadata.json \
288
+ --provenance {forge_version}/provenance-map.json \
289
+ --source-root {source_root}
290
+ ```
291
+
292
+ The helper reads its `--source-root` (falling back to the provenance map's own `source_root` field when the flag is omitted); pass the resolved `{source_root}` so citation resolution runs against the same tree re-extraction read. **Read the emitted JSON — do NOT recompute the set operations by eye; an LLM set-diff can silently pass a dropped or orphaned entry:**
293
+
294
+ - `missing[]` — documented exports (metadata `exports[]`) with no provenance entry: a coverage gap.
295
+ - `orphaned[]` — provenance `entries[].export_name` whose export was removed but the entry remains.
296
+ - `stale[]` — entries whose `source_file:source_line` no longer resolves; each carries a `reason` of `file-missing`, `line-out-of-bounds`, or `line-invalid`. Internal names are canonicalized through `reexport_map` before the diff, so a barrel-renamed export does not read as missing or orphaned.
297
+ - `summary.stale_check` — `"checked"` when citations were resolved against the source tree, or `"skipped-no-source-root"` when no source root resolved on disk (the completeness + orphan diffs still ran; `stale` is empty by construction, not clean-by-verification).
298
+
299
+ Map `status` to the `Provenance:` line of the §4 evidence report's Validation Summary: `PASS` when `status == "pass"`, `WARN` when `status == "findings"` (this is the persistent record — step 5 §5's table was rendered before the provenance map existed, so it necessarily showed this row as deferred). Provenance findings are **advisory** — they do not block the update. Surface each `missing` / `orphaned` / `stale` entry in the evidence report's Validation Summary so the user can decide, and note when `stale_check` was `skipped-no-source-root`.
300
+
301
+ **Graceful degradation:** if neither probe path resolves (no `uv` / script available), fall back to the manual set comparison the script encapsulates — enumerate metadata `exports[]` and provenance `entries[].export_name` (canonicalizing internal names through `reexport_map`), diff the two sets for missing/orphaned entries, and spot-check that each `source_file:source_line` still points at a real line in the source tree. Prefer the script — it does this deterministically.
302
+
255
303
  ### 7. Run Post-Write Validation (Deferred from Step 05)
256
304
 
257
305
  External tool checks deferred from step 5 now run against the written files.
@@ -284,20 +332,7 @@ Record findings in the evidence report (section 4), including any `description_g
284
332
 
285
333
  **If skill-check unavailable:** Skip with note — structural checks from step 5 are sufficient.
286
334
 
287
- ### 8. Present MENU OPTIONS
288
-
289
- Display: "**Proceeding to report...**"
290
-
291
- #### Menu Handling Logic:
292
-
293
- - After all writes verified and post-write validation complete, immediately load, read entire file, then execute {nextStepFile}
294
-
295
- #### EXECUTION RULES:
296
-
297
- - This is an auto-proceed step with no user choices
298
- - Proceed directly to report after verification
299
-
300
- ## CRITICAL STEP COMPLETION NOTE
335
+ ### 8. Route to Next Step
301
336
 
302
- ONLY WHEN all files have been written and verified will you load {nextStepFile} to display the change report.
337
+ This step auto-proceeds — no user choices. Once all files have been written and verified and post-write validation is complete, display "**Proceeding to report...**", then load, fully read, and execute `{nextStepFile}` to display the change report.
303
338
 
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # ///
5
+ """Deterministic NEW_FILE detection for skf-update-skill (detect-changes Category D).
6
+
7
+ `skf-hash-content compare` classifies files already tracked in the provenance
8
+ map (UNCHANGED / MODIFIED_FILE / DELETED_FILE) but by design cannot report a
9
+ file that is present in source yet absent from the provenance map — a NEW_FILE.
10
+ Deriving that set is a set-difference plus a filter with exactly one correct
11
+ answer per input: take every `source_file` in the scripts/assets inventory
12
+ (emitted by `skf-detect-scripts-assets detect`), subtract the paths already in
13
+ `file_entries[].source_file`, and set aside any user-authored `[MANUAL]` path.
14
+ Doing that subtraction in the prompt drifts across runs; doing it here is
15
+ byte-stable.
16
+
17
+ Usage:
18
+ skf-detect-scripts-assets.py detect <source-root> \
19
+ | skf-new-file-diff.py <provenance-map-path>
20
+
21
+ Reads the detect JSON on stdin (needs `scripts_inventory[]` and
22
+ `assets_inventory[]`, each row carrying `source_file`). Reads the provenance
23
+ map at the path argument (canonical `{file_entries: [...]}` object, or a bare
24
+ array of entries).
25
+
26
+ Output JSON (stdout):
27
+ {
28
+ "new_files": [ {"source_file": "...", "kind": "script|asset"}, ... ],
29
+ "skipped_manual": [ "...", ... ], # under scripts/[MANUAL]/ or assets/[MANUAL]/
30
+ "already_tracked": [ "...", ... ], # present in file_entries
31
+ "stats": {"inventory_total": N, "new": N,
32
+ "skipped_manual": N, "already_tracked": N}
33
+ }
34
+
35
+ All three arrays are sorted by source_file; new_files carries the kind of the
36
+ inventory it came from (scripts -> "script", assets -> "asset"). A path found
37
+ in both inventories is counted once, resolved as "script".
38
+
39
+ Exit codes:
40
+ 0 success
41
+ 2 bad input (unreadable/invalid stdin JSON, missing/invalid provenance map)
42
+ """
43
+
44
+ import json
45
+ import re
46
+ import sys
47
+ from pathlib import Path
48
+
49
+ # scripts/[MANUAL]/... or assets/[MANUAL]/... anywhere in the (posix) path
50
+ _MANUAL_RE = re.compile(r"(?:^|/)(?:scripts|assets)/\[MANUAL\]/")
51
+
52
+
53
+ def _fail(msg: str) -> "NoReturn": # type: ignore[valid-type]
54
+ print(json.dumps({"error": msg}), file=sys.stderr)
55
+ sys.exit(2)
56
+
57
+
58
+ def _posix(p: str) -> str:
59
+ return p.replace("\\", "/")
60
+
61
+
62
+ def load_tracked_source_files(provenance_path: Path) -> set[str]:
63
+ """Return the set of file_entries[].source_file already in the provenance map."""
64
+ try:
65
+ data = json.loads(provenance_path.read_text(encoding="utf-8"))
66
+ except (json.JSONDecodeError, OSError) as exc:
67
+ _fail(f"failed to read provenance map {provenance_path}: {exc}")
68
+
69
+ if isinstance(data, list):
70
+ entries = data
71
+ elif isinstance(data, dict):
72
+ entries = data.get("file_entries")
73
+ if entries is None:
74
+ _fail(f"provenance map {provenance_path} has no `file_entries` field")
75
+ if not isinstance(entries, list):
76
+ _fail(f"`file_entries` in {provenance_path} is not an array")
77
+ else:
78
+ _fail(
79
+ f"provenance map {provenance_path} must be an object or array; "
80
+ f"got {type(data).__name__}"
81
+ )
82
+
83
+ tracked: set[str] = set()
84
+ for entry in entries:
85
+ if isinstance(entry, dict):
86
+ sf = entry.get("source_file")
87
+ if isinstance(sf, str) and sf:
88
+ tracked.add(_posix(sf))
89
+ return tracked
90
+
91
+
92
+ def collect_inventory(detect: dict) -> list[tuple[str, str]]:
93
+ """Return [(source_file, kind), ...] from the detect JSON, de-duplicated.
94
+
95
+ Scripts win over assets when a path appears in both, so kind is stable.
96
+ """
97
+ seen: dict[str, str] = {}
98
+ for key, kind in (("scripts_inventory", "script"), ("assets_inventory", "asset")):
99
+ rows = detect.get(key)
100
+ if rows is None:
101
+ continue
102
+ if not isinstance(rows, list):
103
+ _fail(f"`{key}` in detect JSON is not an array")
104
+ for row in rows:
105
+ if not isinstance(row, dict):
106
+ _fail(f"`{key}` entry is not an object: {row!r}")
107
+ sf = row.get("source_file")
108
+ if not isinstance(sf, str) or not sf:
109
+ _fail(f"`{key}` entry missing string `source_file`: {row!r}")
110
+ sf = _posix(sf)
111
+ seen.setdefault(sf, kind) # first inventory wins the kind
112
+ return sorted(seen.items())
113
+
114
+
115
+ def diff(detect: dict, tracked: set[str]) -> dict:
116
+ new_files: list[dict] = []
117
+ skipped_manual: list[str] = []
118
+ already_tracked: list[str] = []
119
+
120
+ inventory = collect_inventory(detect)
121
+ for sf, kind in inventory:
122
+ if sf in tracked:
123
+ already_tracked.append(sf)
124
+ elif _MANUAL_RE.search(sf):
125
+ skipped_manual.append(sf)
126
+ else:
127
+ new_files.append({"source_file": sf, "kind": kind})
128
+
129
+ return {
130
+ "new_files": new_files,
131
+ "skipped_manual": sorted(skipped_manual),
132
+ "already_tracked": sorted(already_tracked),
133
+ "stats": {
134
+ "inventory_total": len(inventory),
135
+ "new": len(new_files),
136
+ "skipped_manual": len(skipped_manual),
137
+ "already_tracked": len(already_tracked),
138
+ },
139
+ }
140
+
141
+
142
+ def main(argv: list[str]) -> int:
143
+ if len(argv) != 2:
144
+ _fail("usage: skf-new-file-diff.py <provenance-map-path> (detect JSON on stdin)")
145
+
146
+ provenance_path = Path(argv[1])
147
+ if not provenance_path.exists():
148
+ _fail(f"provenance map not found: {provenance_path}")
149
+
150
+ raw = sys.stdin.read()
151
+ if not raw.strip():
152
+ _fail("no detect JSON on stdin")
153
+ try:
154
+ detect = json.loads(raw)
155
+ except json.JSONDecodeError as exc:
156
+ _fail(f"invalid detect JSON on stdin: {exc}")
157
+ if not isinstance(detect, dict):
158
+ _fail(f"detect JSON must be an object; got {type(detect).__name__}")
159
+
160
+ tracked = load_tracked_source_files(provenance_path)
161
+ result = diff(detect, tracked)
162
+ print(json.dumps(result, indent=2))
163
+ return 0
164
+
165
+
166
+ if __name__ == "__main__":
167
+ sys.exit(main(sys.argv))
@@ -7,9 +7,9 @@ description: Pre-code stack feasibility verification against architecture and PR
7
7
 
8
8
  ## Overview
9
9
 
10
- Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping. This is a read-only workflow — it never modifies skills or input documents, only reads and produces a feasibility report. Every verdict must cite specific APIs, types, or function signatures from the generated skills.
10
+ Cross-references generated skills against architecture and PRD documents to produce a feasibility report with evidence-backed integration verdicts, coverage analysis, and requirements mapping. Read-only: it reads skills and input documents and writes only the feasibility report (see Workflow Rules).
11
11
 
12
- **Schema contract:** This skill is the PRODUCER of the feasibility report schema defined under the SKF shared references (`_bmad/skf/shared/references/feasibility-report-schema.md` in installed mode; `src/shared/references/feasibility-report-schema.md` in a dev checkout). All report outputs emit `schemaVersion: "1.0"` in frontmatter, use only the defined verdict tokens (`Verified|Plausible|Risky|Blocked` per pair; `FEASIBLE|CONDITIONALLY_FEASIBLE|NOT_FEASIBLE` overall), follow the fixed section-heading order, and are written through `src/shared/scripts/skf-atomic-write.py write` to both the timestamped file and the stable `-latest.md` copy.
12
+ **Schema contract:** This skill is the producer of the SKF shared feasibility report schema every report conforms to it.
13
13
 
14
14
  ## Conventions
15
15
 
@@ -53,34 +53,20 @@ These rules apply to every step in this workflow:
53
53
  |--------|--------|
54
54
  | **Inputs** | architecture_doc_path [required], prd_path [optional], previous_report_path [optional] |
55
55
  | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--architecture-doc <path>` (skip step 1 prompt for the required input); `--prd <path>` (skip step 1 prompt for the optional PRD); `--previous-report <path>` (skip step 1 prompt for delta comparison) |
56
- | **Gates** | step 1: Input Gate [use args] | step 6: Confirm Gate [C] |
56
+ | **Gates** | step 1 Input Gate (use args); step 6 Report Menu ([R] review / [X] exit, headless default X). Steps 2-3 also hold elective vacuous-analysis guards (0% coverage, all-Blocked) that only fire in degenerate cases; every guard auto-resolves to Continue in headless. |
57
57
  | **Outputs** | `feasibility-report-{projectSlug}-{timestamp}.md` and `feasibility-report-{projectSlug}-latest.md` (copy, not symlink) per the SKF shared feasibility report schema (`_bmad/skf/shared/references/feasibility-report-schema.md`; `src/shared/references/…` in a dev checkout) — with integration verdicts, coverage analysis, recommendations, and evidence sources; plus `verify-stack-result-{timestamp}.json` and `verify-stack-result-latest.json` |
58
58
  | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Per-flag args (`--architecture-doc`, `--prd`, `--previous-report`) consumed at the gates that would otherwise prompt. |
59
- | **Exit codes** | See "Exit Codes" below |
60
-
61
- ## Exit Codes
62
-
63
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
64
-
65
- | Code | Meaning | Raised by |
66
- | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
67
- | 0 | success | step 7 (terminal) |
68
- | 2 | input-missing / input-invalid | step 1 §1 (headless missing `architecture-doc` arg, or invalid path) → `input-missing`; non-existent file → `input-invalid` |
69
- | 3 | resolution-failure | step 1 §2 (`{skills_output_folder}` does not exist or is empty); step 1 §3 (forge_data_folder unconfigured) |
70
- | 4 | write-failure | On-Activation §3 pre-flight write probe; step 1 §4 (atomic write of report skeleton failed); step 6 §4b (result-contract write failed) |
71
- | 5 | state-conflict | step 1 §3 (fewer than 2 valid skills found — stack requires ≥2); step 1 §1 (`previousReport` resolves to same inode as `{outputFile}`); step 6 §1 (report section order or schemaVersion mismatch — schema-violation) |
72
- | 6 | user-cancelled | step 1 §1 prompt cancelled; any prompt that accepted `cancel`/`exit`/`:q`; step 6 menu cancelled |
73
- | 7 | inventory-unreliable | step 1 §2 (>20% subagent failures or enumerate-stack-skills warnings exceed budget) |
59
+ | **Exit codes** | See `references/exit-codes.md` |
74
60
 
75
61
  ## Result Contract (Headless)
76
62
 
77
- When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
63
+ When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every headless hard halt emits the same envelope shape on **stderr** with `status: "error"`:
78
64
 
79
65
  ```
80
66
  SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null","report_latest_path":"…|null","overall_verdict":"…|null","coverage_percentage":0,"recommendation_count":0,"exit_code":0,"halt_reason":null}
81
67
  ```
82
68
 
83
- `status` is `"success"` on the terminal happy path, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"skills-folder-missing"`, `"insufficient-skills"`, `"forge-folder-unconfigured"`, `"previous-report-collision"`, `"inventory-unreliable"`, `"schema-violation"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above. `overall_verdict` uses the schema tokens (`FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`).
69
+ `status` is `"success"` on the terminal happy path, `"error"` on any halt. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"skills-folder-missing"`, `"insufficient-skills"`, `"forge-folder-unconfigured"`, `"resolution-failure"`, `"previous-report-collision"`, `"inventory-unreliable"`, `"schema-violation"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches `references/exit-codes.md` (the `analysis-halted` / exit-8 gates are interactive-only, so they never reach this envelope). `overall_verdict` uses the schema tokens (`FEASIBLE`/`CONDITIONALLY_FEASIBLE`/`NOT_FEASIBLE`).
84
70
 
85
71
  ## On Activation
86
72
 
@@ -91,7 +77,7 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
91
77
  2. **Compute run-scoped variables** (same place as config so every stage can reference them without re-derivation):
92
78
  - `project_slug` ← slugify `project_name` (lowercase, hyphens only, no unicode, no whitespace)
93
79
  - `timestamp` ← UTC `YYYYMMDD-HHmmss` captured at activation time
94
- - These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently. (Computing them here resolves an order-of-operations bug where the §1 §3 inode-collision check referenced `{outputFile}` before `{project_slug}` and `{timestamp}` were defined.)
80
+ - These two combine in init.md §4 into `{outputFile}` per the stage frontmatter template, but the values themselves are fixed for the entire workflow run — every later reference to `{outputFile}` resolves consistently.
95
81
 
96
82
  3. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
97
83
 
@@ -119,6 +105,10 @@ SKF_VERIFY_STACK_RESULT_JSON: {"status":"success|error","report_path":"…|null"
119
105
 
120
106
  Stash all four as workflow-context variables. Stage files reference them directly — no conditional at the usage site. Empty-string overrides cleanly fall through to the bundled default.
121
107
 
108
+ The same merge resolves `workflow.on_complete` (default empty = no-op); report.md §5 executes it, if non-empty, at the terminal stage.
109
+
110
+ 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 load their file/glob contents as facts — the bundled default glob is `{project-root}/**/project-context.md`); then execute each entry in `workflow.activation_steps_append` after activation completes.
111
+
122
112
  5. **Pre-flight write probe.** Verify `{outputFolderPath}` is writable. A read-only mount, full disk, or permissions-denied path otherwise only surfaces at init.md §4 atomic write — by then the user has already gone through the input prompts:
123
113
 
124
114
  ```bash
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-verify-stack.
4
+ # Team overrides: _bmad/custom/skf-verify-stack.toml under {project-root}
5
+ # Personal overrides: _bmad/custom/skf-verify-stack.user.toml under {project-root}
4
6
 
5
7
  [workflow]
6
8
 
@@ -34,6 +36,12 @@ persistent_facts = [
34
36
  "file:{project-root}/**/project-context.md",
35
37
  ]
36
38
 
39
+ # Instruction executed when the workflow reaches its terminal stage (after the
40
+ # feasibility report + result JSON are written). Empty = no-op. Use to route
41
+ # the verdict onward or trigger a downstream step without forking the skill.
42
+
43
+ on_complete = ""
44
+
37
45
  # --- Optional asset overrides ---
38
46
  #
39
47
  # Lift the canonical asset paths so orgs can substitute house-style copies
@@ -43,8 +51,9 @@ report_template_path = ""
43
51
  integration_rules_path = ""
44
52
  coverage_patterns_path = ""
45
53
 
46
- # Override the destination directory for the feasibility report. Empty =
47
- # use {forge_data_folder} from config.yaml. Useful for orgs that want
48
- # verification artifacts to land outside the per-skill forge tree.
54
+ # Override the destination directory for the feasibility report only. Empty =
55
+ # use {forge_data_folder} from config.yaml. The result-contract JSON
56
+ # (verify-stack-result-*.json) always stays in {forge_data_folder} as a stable
57
+ # path for pipeline consumers, so redirecting this does not relocate it.
49
58
 
50
59
  output_folder_path = ""