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,6 +1,27 @@
1
1
  ---
2
2
  nextStepFile: 'execute.md'
3
3
  versionPathsKnowledge: 'knowledge/version-paths.md'
4
+ # Read-side inventory helpers (reads, not atomicity-critical). §2 uses
5
+ # `{manifestOpsHelper} read` (manifest parse + v1→v2 migration + corrupt-JSON
6
+ # detection); §3 uses `{skillInventoryHelper}` (on-disk scan + exports∪on-disk
7
+ # diff for orphan detection) and `{manifestOpsHelper} affected-versions <skill>`
8
+ # (numeric semver-descending order, so 0.10.0 precedes 0.9.0 — LLM-unreliable).
9
+ # Probe each in order (installed SKF path first, src/ fallback); first hit wins.
10
+ # If neither candidate resolves, the section computes the result in-prompt.
11
+ manifestOpsProbeOrder:
12
+ - '{project-root}/_bmad/skf/shared/scripts/skf-manifest-ops.py'
13
+ - '{project-root}/src/shared/scripts/skf-manifest-ops.py'
14
+ skillInventoryProbeOrder:
15
+ - '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
16
+ - '{project-root}/src/shared/scripts/skf-skill-inventory.py'
17
+ # Standalone single-line result-envelope contract emitted at every headless
18
+ # HALT in this step. Loaded in §1 so no error path depends on SKILL.md
19
+ # remaining in context under compaction.
20
+ headlessContract: 'headless-contract.md'
21
+ # Deterministic recursive byte sizing + human formatting for the §9b
22
+ # blast-radius line. Bundled with this skill (no probe order needed);
23
+ # execute.md §4 reuses the same helper for the canonical `disk_freed`.
24
+ dirSizesHelper: 'scripts/dir-sizes.py'
4
25
  ---
5
26
 
6
27
  <!-- Config: communicate in {communication_language}. -->
@@ -17,6 +38,7 @@ Identify exactly what the user wants to drop — which skill, which version(s),
17
38
  - Do not proceed without explicit user confirmation at the final gate
18
39
  - Do not drop an active version when other non-deprecated versions exist
19
40
  - Present selections clearly so the user can verify scope, mode, and blast radius
41
+ - **Interactive cancel (every gate below):** at any prompt, `cancel` / `exit` / `[X]` / `q` / `:q` → display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`). Stated once here; the §10 commit gate adds its own tip + headless envelope on top of this.
20
42
 
21
43
  ## MANDATORY SEQUENCE
22
44
 
@@ -30,57 +52,74 @@ Read `{versionPathsKnowledge}` completely and extract:
30
52
 
31
53
  You will use these templates and rules to build directory paths and enforce safety guards in the following sections.
32
54
 
55
+ If `{headless_mode}` is true, also read `{headlessContract}` now — it defines the single-line result envelope every HALT below emits, so the shape is in context even if SKILL.md was compacted out.
56
+
33
57
  ### 2. Read Export Manifest
34
58
 
35
- Load `{skills_output_folder}/.export-manifest.json` if it exists.
59
+ **Resolve `{manifestOpsHelper}`** ← first existing path in `{manifestOpsProbeOrder}`. Parse the manifest through it rather than hand-rolling JSON — the helper migrates v1→v2, normalizes `platforms`→`ides`, and reports a parse error deterministically:
60
+
61
+ ```bash
62
+ python3 {manifestOpsHelper} {skills_output_folder} read
63
+ ```
64
+
65
+ Read the JSON result:
36
66
 
37
- **If the file is missing, empty, or contains no `exports` entries:** Treat as an empty manifest proceed to section 3 and rely on the on-disk directory scan. Draft skills (created by `[CS]`/`[QS]`/`[SS]` but never exported) can still be hard-dropped in purge mode. Store `manifest_exists = false` so section 8 (Ask Mode) can restrict the options to purge only. Soft-deprecate is meaningless without a manifest to record the deprecation against.
67
+ - **`status == "error"`** the manifest file exists but is malformed (the helper returns `{"status":"error","error":"Manifest JSON parse error: ..."}`): halt with "**Export manifest is corrupt** at `{skills_output_folder}/.export-manifest.json` fix or remove the file before dropping." HALT (exit code 3, `halt_reason: "manifest-corrupt"`). In headless mode, emit the error envelope per `{headlessContract}` with `skill: null`, `drop_mode: null`, `versions_affected: []`.
68
+ - **`status == "ok"`** — use `result.manifest` (already migrated to v2) as `manifest` for the rest of this step. Set `manifest_exists = true` when `manifest.exports` has at least one entry, else `false` (a missing manifest reads back as an empty `exports` object, so it correctly yields `false`).
38
69
 
39
- **If the file exists with entries:** Parse JSON and verify `schema_version` is `"2"`. If the manifest is v1 (no `schema_version` field), note this but continue treat every entry as having a single active version derived from its current state. Store `manifest_exists = true`.
70
+ When `manifest_exists = false`, section 3's on-disk scan is authoritative: draft skills (created by `[CS]`/`[QS]`/`[SS]` but never exported) can still be hard-dropped in purge mode, and section 8 restricts the options to purge only soft-deprecate is meaningless without a manifest entry to record it against.
40
71
 
41
- **Hard halt condition:** If the file exists but is malformed (not valid JSON), halt with: "**Export manifest is corrupt** at `{skills_output_folder}/.export-manifest.json` fix or remove the file before dropping." HALT (exit code 3, `halt_reason: "manifest-corrupt"`). In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: null`, `drop_mode: null`, `versions_affected: []`.
72
+ **If neither `{manifestOpsProbeOrder}` candidate resolves:** read the manifest file in-prompt a missing/empty file is `manifest_exists = false`; a file with `exports` entries is `true` (no `schema_version` field means v1 treat each entry as a single active version); invalid JSON takes the corrupt-manifest HALT above.
42
73
 
43
74
  ### 3. List Available Skills
44
75
 
45
- Build and display a summary of every skill available to drop. Start with the manifest (if any), then augment with an on-disk scan.
76
+ Build and display a summary of every skill available to drop: every manifest-tracked skill, plus every on-disk skill not in the manifest (draft/orphaned, eligible for purge only).
77
+
78
+ **Manifest-tracked skills** come from the `manifest` resolved in section 2 — for each key in `manifest.exports`, its `active_version` and its `versions` map (with each version's `status`) are already parsed. Order each skill's versions newest-first through the helper rather than by eye:
79
+
80
+ ```bash
81
+ python3 {manifestOpsHelper} {skills_output_folder} affected-versions {skill-name}
82
+ ```
83
+
84
+ `result.affected_versions` is that skill's versions deduped and sorted in the helper's numeric-descending order (see the frontmatter note). Annotate each with its `status` from `manifest.exports.{skill-name}.versions.{version}.status` and mark `active_version` with a trailing `*`.
46
85
 
47
- For each skill in the manifest's `exports` (only if `manifest_exists = true` and entries exist):
86
+ **On-disk (not-in-manifest) skills** come from the inventory helper. **Resolve `{skillInventoryHelper}`** first existing path in `{skillInventoryProbeOrder}`; it scans `{skills_output_folder}/` and computes the exports∪on-disk merge for you — do not re-scan the directory in the prompt:
48
87
 
49
- 1. Read `active_version` from the manifest entry
50
- 2. List every entry in the skill's `versions` map with its `status` field
51
- 3. Mark the active version with a trailing `*`
52
- 4. Sort versions in descending order (newest first) where possible
88
+ ```bash
89
+ python3 {skillInventoryHelper} {skills_output_folder}
90
+ ```
53
91
 
54
- Also scan `{skills_output_folder}/` for any top-level directories that are NOT present in the manifest's `exports` object. Record these as "(not in manifest)"they represent draft or orphaned skills eligible for purge mode only. When the manifest is missing or empty, every on-disk skill appears in this category.
92
+ Any `result.skills[].name` that is **not** a key in `manifest.exports` is a draft or orphaned skill record it as "(not in manifest — purge only)". When the manifest is empty, every on-disk skill lands here. The inventory lists only skills with an on-disk directory, so a manifest entry whose files were already removed still appears above via `manifest.exports`.
55
93
 
56
- **If the combined list is empty** (no manifest entries AND no on-disk skill directories): halt with "**Drop Skill — nothing to drop.** No skills found in `{skills_output_folder}/` and no entries in `.export-manifest.json`. Run `[CS] Create Skill` first." HALT (exit code 3, `halt_reason: "nothing-to-drop"`). In headless mode, emit the error envelope with `skill: null`, `drop_mode: null`, `versions_affected: []`.
94
+ **If the combined roster is empty** (no `manifest.exports` entries AND `result.skills[]` is empty): halt with "**Drop Skill — nothing to drop.** No skills found in `{skills_output_folder}/` and no entries in `.export-manifest.json`. Run `[CS] Create Skill` first." HALT (exit code 3, `halt_reason: "nothing-to-drop"`). In headless mode, emit the error envelope per `{headlessContract}` with `skill: null`, `drop_mode: null`, `versions_affected: []`.
57
95
 
58
- Display the combined list:
96
+ Display the combined list (versions newest-first):
59
97
 
60
98
  ```
61
99
  **Drop Skill — select target**
62
100
 
63
101
  Available skills:
64
102
  1. cognee
65
- - 0.1.0 (deprecated)
66
- - 0.5.0 (archived)
67
103
  - 0.6.0 (active) *
104
+ - 0.5.0 (archived)
105
+ - 0.1.0 (deprecated)
68
106
  2. express
69
107
  - 4.18.0 (active) *
70
108
  3. legacy-helper (not in manifest — purge only)
71
109
  ```
72
110
 
111
+ **If neither helper resolves** (Python/helper unavailable): fall back to the in-prompt computation — list each `manifest.exports` skill (versions with `status`, active marked `*`, ordered newest-first by comparing version components numerically), then scan `{skills_output_folder}/` for top-level directories absent from `manifest.exports` and record them as "(not in manifest — purge only)". If the combined list is empty, take the "nothing to drop" HALT above.
112
+
73
113
  ### 4. Ask Which Skill
74
114
 
75
115
  "**Which skill would you like to drop?**
76
116
  Enter the skill name or its number from the list above, or `cancel` / `exit` / `:q` to abort."
77
117
 
78
- Wait for user input. Accept either the numeric index or the skill name (exact match). **GATE [default: use args]** — If `{headless_mode}` and skill name was provided as argument: select that skill and auto-proceed. If not provided, HALT (exit code 2, `halt_reason: "input-missing"`): "headless mode requires skill name argument." In headless mode, emit the error envelope with `skill: null`, `drop_mode: null`.
118
+ Wait for user input. Accept either the numeric index or the skill name (exact match). **GATE [default: use args]** — If `{headless_mode}` and skill name was provided as argument: select that skill and auto-proceed. If not provided, HALT (exit code 2, `halt_reason: "input-missing"`): "headless mode requires skill name argument." In headless mode, emit the error envelope per `{headlessContract}` with `skill: null`, `drop_mode: null`.
79
119
 
80
- - If the user enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
81
120
  - **If the user's input does not match any listed skill:**
82
121
  - **Interactive:** Re-display the list and ask again.
83
- - **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: skill argument `{supplied value}` does not match any listed skill." Emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: null`, `drop_mode: null`, `versions_affected: []`.
122
+ - **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: skill argument `{supplied value}` does not match any listed skill." Emit the error envelope per `{headlessContract}` with `skill: null`, `drop_mode: null`, `versions_affected: []`.
84
123
 
85
124
  Store the selection as `target_skill`. Also store `target_in_manifest = true` if the selected skill has an entry in the manifest, `false` otherwise — subsequent sections use this flag to restrict the available drop options.
86
125
 
@@ -122,8 +161,6 @@ Store the selection as `target_skill`. Also store `target_in_manifest = true` if
122
161
 
123
162
  Wait for user selection.
124
163
 
125
- - If the user enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
126
-
127
164
  **If [N] Specific version:**
128
165
 
129
166
  "**Which version?** Enter the version string (e.g. `0.5.0`)."
@@ -131,7 +168,7 @@ Wait for user selection.
131
168
  Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`.
132
169
 
133
170
  - **If it does not match (interactive):** repeat the prompt.
134
- - **If it does not match (headless — `{headless_mode}` is true):** the supplied `version` argument is unparseable or absent from the `versions` map — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions." Emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: []`.
171
+ - **If it does not match (headless — `{headless_mode}` is true):** the supplied `version` argument is unparseable or absent from the `versions` map — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions." Emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: []`.
135
172
 
136
173
  Set `target_versions = [<selected version>]` and `is_skill_level = false`.
137
174
 
@@ -158,19 +195,21 @@ Set `target_versions = "all"` and `is_skill_level = true`.
158
195
 
159
196
  **(b)** Use the `[A] All versions` option to drop every version of `{target_skill}` at once."
160
197
 
161
- HALT (exit code 5, `halt_reason: "active-version-guard-refused"`). In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: ["{version}"]`. Do not proceed.
198
+ HALT (exit code 5, `halt_reason: "active-version-guard-refused"`). In headless mode, emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: ["{version}"]`. Do not proceed.
162
199
 
163
200
  c. If the count is `0` → the active version is the ONLY version; allow the drop to continue (it is functionally equivalent to a skill-level drop on a single-version skill)
164
201
 
165
202
  ### 8. Ask Mode
166
203
 
167
- **If `target_in_manifest = false`:** Skip this prompt — soft-deprecate is meaningless without a manifest entry to mark. Force `drop_mode = "purge"` and inform the user: "**Mode forced to purge** — `{target_skill}` has no manifest entry, so there is nothing to deprecate. The skill's on-disk directories will be deleted."
204
+ **If `target_in_manifest = false`:** Skip this prompt — soft-deprecate is meaningless without a manifest entry to mark. Force `drop_mode = "purge"` (record `mode_source = "draft-skill-forced-purge"`) and inform the user: "**Mode forced to purge** — `{target_skill}` has no manifest entry, so there is nothing to deprecate. The skill's on-disk directories will be deleted."
168
205
 
169
206
  **If `target_in_manifest = true`:**
170
207
 
171
- **If `{defaultMode}` is non-empty (`"deprecate"` or `"purge"`)**: skip the prompt, set `drop_mode = "{defaultMode}"`, and log the override source for the headless decision trail (`"customize.toml.workflow.default_mode"`).
208
+ **If a `mode` argument was supplied at invocation:** an explicit `mode` arg is the per-run override and takes precedence over `{defaultMode}`. If it is `"deprecate"` or `"purge"`, set `drop_mode` from it and record the decision source `mode_source = "--mode argument"`. If a `mode` arg was supplied but is not one of `deprecate` / `purge`, HALT (exit code 2, `halt_reason: "input-invalid"`): "invalid `--mode` value `{supplied}` — expected `deprecate` or `purge`." In headless mode, emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`.
209
+
210
+ **Else if `{defaultMode}` is non-empty (`"deprecate"` or `"purge"`)**: skip the prompt, set `drop_mode = "{defaultMode}"`, and record the decision source `mode_source = "customize.toml.workflow.default_mode"` for the headless decision trail.
172
211
 
173
- **Otherwise:**
212
+ **Otherwise (interactive):** If `{headless_mode}` is true at this point (no `mode` arg and no `{defaultMode}`), there is no input to prompt for — HALT (exit code 2, `halt_reason: "input-missing"`): "headless mode requires `--mode deprecate|purge` or `default_mode` in customize.toml to set the drop mode." Emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`. Otherwise, prompt the user:
174
213
 
175
214
  "**How should this be dropped?**
176
215
 
@@ -180,9 +219,7 @@ Set `target_versions = "all"` and `is_skill_level = true`.
180
219
 
181
220
  Wait for user selection.
182
221
 
183
- - If the user enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
184
-
185
- Set `drop_mode` to `"deprecate"` (on D) or `"purge"` (on P).
222
+ Set `drop_mode` to `"deprecate"` (on D) or `"purge"` (on P), and record `mode_source = "interactive-prompt"`.
186
223
 
187
224
  ### 9. Compute Affected Directories
188
225
 
@@ -205,19 +242,25 @@ If `drop_mode == "deprecate"`, record the list but present it as "retained" in t
205
242
 
206
243
  #### 9b. Compute Blast-Radius Metrics (for §10 summary)
207
244
 
208
- Compute three scalars to put in front of the path list at §10. The user's "I didn't know it would touch THAT" footgun is real — 12 paths can hide 50MB of on-disk content and a sweep across every IDE's managed section. A one-line summary makes the scale legible before the gate.
245
+ Compute three scalars to put in front of the path list at §10, so the user sees the scale of an irreversible drop before scanning individual paths:
209
246
 
210
247
  1. **`versions_count`** — the number of skill versions in scope:
211
248
  - Version-level drop: `len(target_versions)` (typically `1`)
212
249
  - Skill-level drop: count of non-deprecated versions in `exports.{target_skill}.versions` (the deprecated ones are already absent from the active managed sections)
213
250
 
214
- 2. **`bytes_total`** — recursive sum of byte sizes for every path in `affected_directories` that exists on disk. For each path, walk it and sum file sizes (`du -sb {path}` or equivalent). Skip non-existent paths silently — the §10 display is best-effort. Convert to a human-readable string for display (e.g. `"4.2 MB"`, `"812 KB"`); store the raw integer alongside as `bytes_total_raw` if a downstream step wants exact arithmetic. Defense in depth with execute.md §4 — that section will recompute per-path sizes for the canonical `disk_freed` reporting; this pre-compute is purely for the gate display and may slightly disagree if files change between gate and execute (acceptable for an "approximate" label).
251
+ 2. **`bytes_total`** — the on-disk size of `affected_directories`. Delegate the recursive sum and the human label to the sizing helper rather than adding file sizes in-prompt:
252
+
253
+ ```bash
254
+ uv run {dirSizesHelper} sizes {each path in affected_directories, space-separated}
255
+ ```
256
+
257
+ Read `total_human` (e.g. `"4.2 MB"`) as `bytes_total` and `total_bytes` as `bytes_total_raw`; non-existent paths report `exists: false` and drop out of the total. If the helper is unavailable, fall back to `du -sb` per path — the display is best-effort. execute.md §4 re-runs the same helper on the paths it actually deletes for the canonical `disk_freed`, so the two share one method and differ only if files change between this gate and execution.
215
258
 
216
259
  3. **`context_files_count`** — the number of distinct context files the §3 rebuild loop will rewrite:
217
260
  - Read `config.yaml.ides`
218
- - For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use `AGENTS.md` fallback for unknown IDEs)
261
+ - For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use the `{unknownIdeDefaultContextFile}` fallback for unknown IDEs)
219
262
  - Deduplicate by `context_file`
220
- - Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the AGENTS.md fallback)
263
+ - Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the single `{unknownIdeDefaultContextFile}` fallback)
221
264
 
222
265
  Store as `blast_radius = {versions_count, bytes_total, bytes_total_raw, context_files_count}` for §10's summary line.
223
266
 
@@ -246,28 +289,27 @@ Proceed? [Y/N]
246
289
 
247
290
  The `Scope:` line is the §9b-computed `blast_radius` rendered as one line. In `deprecate` mode the `~{bytes_total} on disk` reads as "size that will remain on disk (soft drop — files retained)"; the user is still served by knowing it. In `purge` mode it reads as "approximate disk that will be freed". The wording stays the same — the surrounding `Mode:` field disambiguates intent.
248
291
 
249
- **GATE [default: Y]**If `{headless_mode}`: auto-proceed with [Y], log: "headless: auto-confirmed drop of {target_skill}"
250
-
251
- Wait for explicit user response.
292
+ **Resolve `--dry-run` firstit takes precedence over the headless auto-confirm.** `--dry-run` and `--headless` can be combined (dry-run is the automated-preview path), so a dry-run run must always short-circuit to the preview below and never mutate, even when `{headless_mode}` is true.
252
293
 
253
- **If `--dry-run` was passed**: skip the Y/N prompt entirely. Display the `[DRY RUN]` line followed by a copy-pasteable selection memo so the user can capture this preview in their shell history and re-run it (interactively, picking the same values) when they're ready to commit:
294
+ **If `--dry-run` was passed**: skip the Y/N prompt entirely — do not evaluate the headless auto-confirm gate. Display the `[DRY RUN]` line with the resolved selection, so the user can re-run interactively with the same values when ready to commit:
254
295
 
255
296
  ```
256
297
  **[DRY RUN] No changes were made — preview above shows what would be dropped.**
257
298
 
258
- To repeat this selection later:
299
+ Resolved selection:
259
300
  Skill: {target_skill}
260
301
  Version: {target_versions[0] if is_skill_level == false else "all"}
261
302
  Mode: {Deprecate (soft) | Purge (hard)}
262
-
263
- Re-invoke `/skf-drop-skill {target_skill}` and re-select these values, or
264
- re-run with `--dry-run` to preview again.
265
303
  ```
266
304
 
267
- Then emit the success envelope per SKILL.md "Result Contract (Headless)" with `status: "dry-run"`, the resolved `skill`, `drop_mode`, and `versions_affected`, then HALT (exit code 0). The manifest, filesystem, and context files are untouched.
305
+ Then emit the success envelope per `{headlessContract}` with `status: "dry-run"`, the resolved `skill`, `drop_mode`, and `versions_affected`, then HALT (exit code 0). The manifest, filesystem, and context files are untouched.
306
+
307
+ **Otherwise (not a dry-run) — GATE [default: Y]:** If `{headless_mode}`: auto-proceed with [Y], record `confirm_source = "headless-auto"`, log: "headless: auto-confirmed drop of {target_skill}"
308
+
309
+ Wait for explicit user response.
268
310
 
269
- - **If `Y`** → proceed to section 11
270
- - **If `N`** (or `cancel` / `exit` / `[X]` / `:q`) → "**Cancelled.** No changes were made. (Tip: invoke with `--dry-run` next time to preview the operation without reaching the commit prompt.)" HALT (exit code 6, `halt_reason: "user-cancelled"`). In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with the resolved `skill`, `drop_mode`, and `versions_affected`.
311
+ - **If `Y`** → record `confirm_source = "user-explicit"` and proceed to section 11
312
+ - **If `N`** (or `cancel` / `exit` / `[X]` / `:q`) → "**Cancelled.** No changes were made. (Tip: invoke with `--dry-run` next time to preview the operation without reaching the commit prompt.)" HALT (exit code 6, `halt_reason: "user-cancelled"`). In headless mode, emit the error envelope per `{headlessContract}` with the resolved `skill`, `drop_mode`, and `versions_affected`.
271
313
  - **Any other input** → re-display the confirmation and ask again
272
314
 
273
315
  ### 11. Store Decisions in Context
@@ -280,12 +322,10 @@ Store the following decisions in workflow context for step 2:
280
322
  - `drop_mode` — `"deprecate"` or `"purge"` (always `"purge"` when `target_in_manifest = false`)
281
323
  - `is_skill_level` — boolean (true if all versions; always true when `target_in_manifest = false`)
282
324
  - `affected_directories` — list of absolute directory paths that step 2 will delete in purge mode (or retain in deprecate mode)
325
+ - `mode_source` — where `drop_mode` was decided, set inline at §8 (one of the four sources named there)
326
+ - `confirm_source` — how the §10 gate was cleared, set inline at §10 (`"headless-auto"` or `"user-explicit"`)
283
327
 
284
328
  ### 12. Load Next Step
285
329
 
286
- Load, read the full file, and then execute `{nextStepFile}`.
287
-
288
- ## CRITICAL STEP COMPLETION NOTE
289
-
290
- ONLY WHEN the user has confirmed with `Y` at the confirmation gate AND all selection decisions have been stored in context, will you then load and read fully `{nextStepFile}` to execute the drop.
330
+ `{nextStepFile}` performs the destructive mutation, so reach it only after the §10 gate returned `Y` and §11 stored the decisions — chaining any earlier would drop without the user's explicit consent (the sequence above enforces this ordering). Load, read the full file, and then execute it.
291
331
 
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.10"
4
+ # ///
5
+ """Deterministic directory-size measurement for skf-drop-skill.
6
+
7
+ The drop workflow twice needs an exact recursive byte total plus a stable
8
+ human label ("4.2 MB" / "812 KB"): select.md §9b renders the blast-radius
9
+ line ahead of the confirmation gate, and execute.md §4 reports the canonical
10
+ `disk_freed`. Summing file sizes and rounding a unit in the prompt has one
11
+ correct answer per input, so it belongs here — identical input yields
12
+ identical output, and the gate preview and the post-purge report agree on the
13
+ method (they can still differ only because they read at different times).
14
+
15
+ Two operations:
16
+
17
+ sizes <path>... recursive byte size of each path (best-effort: a
18
+ non-existent path reports exists=false, bytes=0 and is
19
+ excluded from the total). Symlinks are measured by the
20
+ size of the link itself, never followed.
21
+ humanize <bytes>... sum a set of already-measured byte counts and format
22
+ one human label. execute.md §4 feeds it the sizes of
23
+ the paths that were actually deleted.
24
+
25
+ Output is a single JSON object on stdout. Exit codes: 0 ok; 2 usage error
26
+ (missing/unknown op, or a non-integer byte count for `humanize`).
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ import os
32
+ import sys
33
+
34
+ # 1024-based, matching the `du -h` feel the step files reference. Labelled
35
+ # KB/MB for readability; the raw integer total travels alongside for any
36
+ # consumer that needs exact arithmetic.
37
+ _UNITS = ["B", "KB", "MB", "GB", "TB", "PB"]
38
+
39
+
40
+ def humanize_bytes(n: int) -> str:
41
+ size = float(n)
42
+ for unit in _UNITS:
43
+ if size < 1024 or unit == _UNITS[-1]:
44
+ if unit == "B":
45
+ return f"{int(size)} {unit}"
46
+ return f"{size:.1f} {unit}"
47
+ size /= 1024
48
+ return f"{int(n)} B" # unreachable; keeps type checkers happy
49
+
50
+
51
+ def dir_bytes(path: str) -> int:
52
+ """Recursive byte size of a file, directory, or symlink (link not followed)."""
53
+ if os.path.islink(path):
54
+ return os.lstat(path).st_size
55
+ if os.path.isfile(path):
56
+ return os.path.getsize(path)
57
+ total = 0
58
+ for root, _dirs, files in os.walk(path):
59
+ for name in files:
60
+ fp = os.path.join(root, name)
61
+ try:
62
+ total += os.lstat(fp).st_size
63
+ except OSError:
64
+ pass
65
+ return total
66
+
67
+
68
+ def _op_sizes(paths: list[str]) -> dict:
69
+ entries = []
70
+ total = 0
71
+ for p in paths:
72
+ exists = os.path.exists(p) or os.path.islink(p)
73
+ b = dir_bytes(p) if exists else 0
74
+ if exists:
75
+ total += b
76
+ entries.append({"path": p, "exists": exists, "bytes": b, "human": humanize_bytes(b)})
77
+ return {
78
+ "status": "ok",
79
+ "op": "sizes",
80
+ "paths": entries,
81
+ "total_bytes": total,
82
+ "total_human": humanize_bytes(total),
83
+ }
84
+
85
+
86
+ def _op_humanize(raw: list[str]) -> dict:
87
+ total = 0
88
+ for value in raw:
89
+ try:
90
+ total += int(value)
91
+ except (TypeError, ValueError):
92
+ print(
93
+ json.dumps({"status": "error", "error": f"not an integer byte count: {value!r}"}),
94
+ file=sys.stderr,
95
+ )
96
+ sys.exit(2)
97
+ return {"status": "ok", "op": "humanize", "total_bytes": total, "total_human": humanize_bytes(total)}
98
+
99
+
100
+ def main(argv: list[str]) -> int:
101
+ if not argv:
102
+ print(json.dumps({"status": "error", "error": "usage: dir-sizes.py {sizes|humanize} <args>"}), file=sys.stderr)
103
+ return 2
104
+ op, args = argv[0], argv[1:]
105
+ if op == "sizes":
106
+ print(json.dumps(_op_sizes(args)))
107
+ return 0
108
+ if op == "humanize":
109
+ print(json.dumps(_op_humanize(args)))
110
+ return 0
111
+ print(json.dumps({"status": "error", "error": f"unknown op: {op!r}"}), file=sys.stderr)
112
+ return 2
113
+
114
+
115
+ if __name__ == "__main__":
116
+ sys.exit(main(sys.argv[1:]))
@@ -7,20 +7,21 @@ description: Package for distribution and inject context into CLAUDE.md/AGENTS.m
7
7
 
8
8
  ## Overview
9
9
 
10
- Packages a completed skill as an agentskills.io-compliant package, generates context snippets, and updates the managed section in CLAUDE.md/.cursorrules/AGENTS.md for platform-aware context injection. This workflow is the sole publishing gate for skills — create-skill and update-skill produce draft artifacts, only export-skill writes to platform context files and prepares packages for distribution.
10
+ Packages a completed skill as an agentskills.io-compliant package, generates context snippets, and updates the managed section in CLAUDE.md/.cursorrules/AGENTS.md for platform-aware context injection. It is the sole publishing gate — create-skill/update-skill produce drafts; only export writes platform context files and distribution packages.
11
11
 
12
12
  ## Conventions
13
13
 
14
14
  - Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
15
+ - **Module-level path exception:** bare `knowledge/` and `shared/` paths resolve from the SKF module root (`{project-root}/_bmad/skf/` installed, `src/` in dev), not the skill root (e.g. `knowledge/version-paths.md`, `shared/health-check.md`).
15
16
  - `references/` holds prompt content carved out of SKILL.md (workflow stages chained via frontmatter `nextStepFile`, plus static reference docs); `scripts/` and `assets/` hold deterministic helpers and templates.
16
17
  - `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
17
18
  - `{project-root}`-prefixed paths resolve from the project working directory.
18
19
  - `{skill-name}` resolves to the skill directory's basename.
19
- - **Cross-skill data coupling (export-skill is a hub):** `assets/managed-section-format.md` is loaded by `skf-drop-skill/references/execute.md` and `skf-rename-skill/references/execute.md` (IDE→context-file mapping table and four-case logic). `references/update-context.md` §4a manifest-schema documentation is the source of truth for the v2 schema enforced by `skf-manifest-ops.py`. Other skills depend on these files at install time — schema-breaking changes here require coordinated updates across at least three skills.
20
+ - **Cross-skill data coupling:** `assets/managed-section-format.md` (loaded by drop-skill and rename-skill's `execute.md`) and `references/update-context.md` §4a (v2 manifest schema enforced by `skf-manifest-ops.py`) are the source of truth for those contracts — schema-breaking changes here need coordinated updates across those skills.
20
21
 
21
22
  ## Role
22
23
 
23
- You are a delivery and packaging specialist collaborating with a skill developer. You bring expertise in skill packaging, ecosystem compliance, and context injection patterns, while the user brings their completed skill and distribution requirements.
24
+ You are a delivery and packaging specialist collaborating with a skill developer, pairing your skill-packaging, ecosystem-compliance, and context-injection expertise with their completed skill and distribution requirements.
24
25
 
25
26
  ## Workflow Rules
26
27
 
@@ -48,7 +49,7 @@ These rules apply to every step in this workflow:
48
49
  | Aspect | Detail |
49
50
  |--------|--------|
50
51
  | **Inputs** | `skill_name` [one or more, required unless `--all`] |
51
- | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--all` (export every non-deprecated skill in `.export-manifest.json`); `--dry-run` (resolve and stage everything but exit before §4 writes to context files; envelope `status="dry-run"`) |
52
+ | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--all` (export every non-deprecated skill in `.export-manifest.json`); `--dry-run` (stage everything but write nothing context files, manifest, and snippet are previewed only; the run completes read-only through the terminal step; `status="dry-run"`) |
52
53
  | **Gates** | step 1: single Confirm Gate [C] for the whole batch | step 4: single Confirm Gate [C] for the whole batch |
53
54
  | **Outputs** | Updated `.export-manifest.json` (every skill in the batch), updated context files (CLAUDE.md/AGENTS.md/.cursorrules), per-skill `context-snippet.md`, per-run result contract `export-skill-result-{timestamp}.json` and `export-skill-result-latest.json` |
54
55
  | **Multi-skill mode** | Activated when more than one skill is selected (via `--all`, multi-selection, or multi-argument invocation). See `references/load-skill.md` §1c for the per-step iteration map. |
@@ -59,14 +60,14 @@ These rules apply to every step in this workflow:
59
60
 
60
61
  Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
61
62
 
62
- | Code | Meaning | Raised by |
63
+ | Code | Meaning | Raised by (halt site → `halt_reason`) |
63
64
  | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
64
65
  | 0 | success | step 7 (terminal); also `status="dry-run"` when `--dry-run` is set |
65
- | 2 | input-missing / input-invalid | step 1 (no `skill_name` and no `--all` in headless) `input-missing`; non-existent or malformed skill → `input-invalid` |
66
- | 3 | resolution-failure | step 1 §2 (no skills found / required artifacts missing); step 1 §1c (multi-skill batch contains a stack-skill that requires re-composition) |
67
- | 4 | write-failure | On-Activation §3 pre-flight write probe; step 4 §6 (managed-section rewrite); step 4 §9 (manifest write); step 6 §4 (result-contract write) |
68
- | 5 | state-conflict | step 4 §3b/§4c.1 (orphan context-files or manifest-orphan rows when user selects [c] Cancel); step 4 (malformed `<!-- SKF:BEGIN/END -->` markers in target context file) |
69
- | 6 | user-cancelled | step 1 §6 confirmation gate `[X]`/cancel; step 4 §8 confirmation gate `[X]`/cancel; any prompt that accepted `cancel`/`exit`/`:q` |
66
+ | 2 | input-missing | step 1 §1 — headless run with no `skill_name` and no `--all` (a non-interactive run cannot answer the skill-selection menu) → `input-missing` |
67
+ | 3 | resolution-failure | step 1 §1 (discovery finds no skills on disk / in the manifest); step 1 §2 (a named skill's required artifacts are missing or its metadata is invalid — export-gate FAIL); multi-skill batch (any skill failing §2 validation halts the whole batch) → `resolution-failure` |
68
+ | 4 | write-failure | On-Activation §5 pre-flight write probe → `write-failed`; step 4 §3b / §9 / §4c.1 (managed-section create/append/rewrite/clear verify fails, or a required helper is unresolvable) → `context-rebuild-failed`; step 4 §9b manifest write `manifest-write-failed` |
69
+ | 5 | state-conflict | step 4 §6 malformed `<!-- SKF:BEGIN/END -->` markers in the target context file (`<!-- SKF:BEGIN` present, `<!-- SKF:END -->` missing) `malformed-markers` |
70
+ | 6 | user-cancelled | step 1 §6 gate `[X]`/cancel; step 1 §1b snippet-root probe (a)/(c); step 4 §8 gate `[X]`/cancel; step 4 §4c.1 orphan-row (c) Cancel; any prompt accepting `cancel`/`exit`/`:q` → `user-cancelled` |
70
71
 
71
72
  ## Result Contract (Headless)
72
73
 
@@ -76,7 +77,7 @@ When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **st
76
77
  SKF_EXPORT_RESULT_JSON: {"status":"success|error|dry-run","skills":[],"context_files_updated":[],"manifest_path":"…|null","headless_decisions":[],"exit_code":0,"halt_reason":null}
77
78
  ```
78
79
 
79
- `status` is `"success"` on the terminal happy path, `"dry-run"` when `--dry-run` was set and the workflow exited before §4 writes, `"error"` on any HALT. `halt_reason` is one of: `null` (success / dry-run), `"input-missing"`, `"input-invalid"`, `"resolution-failure"`, `"stack-redirect"`, `"orphan-cancelled"`, `"malformed-markers"`, `"manifest-write-failed"`, `"context-rebuild-failed"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above.
80
+ `references/result-envelope.md` is the single source for the full field semantics and the `halt_reason` enum halting stages load it directly, so the enum lives in exactly one place. `exit_code` matches the Exit Codes table above.
80
81
 
81
82
  ## On Activation
82
83
 
@@ -105,14 +106,16 @@ SKF_EXPORT_RESULT_JSON: {"status":"success|error|dry-run","skills":[],"context_f
105
106
 
106
107
  If the script fails or is missing, fall back to reading `{skill-root}/customize.toml` directly — the bundled defaults are an empty string for each path scalar.
107
108
 
108
- Apply the path-scalar fallback now so stage files don't have to repeat the conditional logic. For each scalar, if the merged value is empty or absent, use the bundled default:
109
+ Apply the fallback now so stage files don't have to repeat the conditional logic. For each scalar, if the merged value is empty or absent, use the bundled default:
109
110
 
110
111
  - `{managedSectionFormatPath}` ← `workflow.managed_section_format_path` if non-empty, else `assets/managed-section-format.md`
111
112
  - `{snippetFormatPath}` ← `workflow.snippet_format_path` if non-empty, else `assets/snippet-format.md`
112
- - `{exportManifestPath}` ← `workflow.export_manifest_path` if non-empty, else `{skills_output_folder}/.export-manifest.json`
113
+ - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — step 6 skips the hook invocation)
113
114
 
114
115
  Stash all three as workflow-context variables. Stage files reference them directly — no conditional at the usage site.
115
116
 
117
+ **Apply the array surfaces so they are not silent no-ops:** execute each entry in `workflow.activation_steps_prepend` in order now (org-wide pre-flight such as auth, network, or compliance); 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`); then, after activation completes and before the first stage runs, execute each entry in `workflow.activation_steps_append` in order.
118
+
116
119
  5. **Pre-flight write probe.** Verify `{skills_output_folder}` is writable. A read-only mount, full disk, or permissions-denied path otherwise only surfaces at step 4's managed-section rewrite — by then the user has already confirmed the batch:
117
120
 
118
121
  ```bash
@@ -121,6 +124,6 @@ SKF_EXPORT_RESULT_JSON: {"status":"success|error|dry-run","skills":[],"context_f
121
124
  rm "{skills_output_folder}/.skf-write-probe"
122
125
  ```
123
126
 
124
- On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`). In headless mode, emit the error envelope per **Result Contract (Headless)** with `skills: []`, `context_files_updated: []`, `manifest_path: null`.
127
+ On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`). In headless mode, emit the error envelope per `references/result-envelope.md` with `skills: []`, `context_files_updated: []`, `manifest_path: null`.
125
128
 
126
129
  6. Load, read the full file, and then execute `references/load-skill.md` to begin the workflow.
@@ -122,7 +122,7 @@ Target file contains `<!-- SKF:BEGIN` but no matching `<!-- SKF:END -->` marker.
122
122
  When regenerating (Case 3) or creating/appending (Cases 1-2), rebuild the skill index from the **exported skill set** only:
123
123
 
124
124
  1. Read `{skills_output_folder}/.export-manifest.json` (v2 schema — see `knowledge/version-paths.md`) to determine which skills have been explicitly exported and their `active_version` (if no manifest exists, only the current export target qualifies)
125
- 2. For each skill in the exported set, resolve the snippet at `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/context-snippet.md` (i.e., `{skill_package}/context-snippet.md`). Do NOT use glob patterns across version directories — always resolve via manifest `active_version` or `active` symlink
125
+ 2. For each skill in the exported set, resolve the snippet at `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/context-snippet.md` (i.e., `{skill_package}/context-snippet.md`). Do NOT use glob patterns across version directories — always resolve via manifest `active_version` or `active` symlink, falling back to the flat `{skills_output_folder}/{skill-name}/context-snippet.md` path for a skill not yet migrated to the versioned layout
126
126
  3. Count total skills and stack skills (from filtered set only)
127
127
  4. Assemble filtered snippets into managed section
128
128
  5. Sort alphabetically by skill name
@@ -132,7 +132,4 @@ When regenerating (Case 3) or creating/appending (Cases 1-2), rebuild the skill
132
132
 
133
133
  ## Safety Rules
134
134
 
135
- - NEVER modify content outside the `<!-- SKF:BEGIN/END -->` markers
136
- - ALWAYS preserve existing file content above and below markers
137
- - ALWAYS verify file was written correctly after write
138
- - If write fails, report error — do not attempt partial writes
135
+ Only the bytes between `<!-- SKF:BEGIN -->` and `<!-- SKF:END -->` are yours to rewrite; everything above and below is the user's own file (their CLAUDE.md/AGENTS.md/.cursorrules) and must survive byte-for-byte — rewriting outside the markers destroys their content. On write failure, report and stop — never leave a partial write. Route writes through the atomic-write / rebuild-managed-sections helpers: they verify byte-identity of the content outside the markers after each write, so no separate hand-check is needed.
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-export-skill.
4
+ # Team overrides: _bmad/custom/skf-export-skill.toml (under {project-root})
5
+ # Personal overrides: _bmad/custom/skf-export-skill.user.toml (under {project-root})
4
6
 
5
7
  [workflow]
6
8
 
@@ -34,6 +36,14 @@ persistent_facts = [
34
36
  "file:{project-root}/**/project-context.md",
35
37
  ]
36
38
 
39
+ # Optional post-export hook. When non-empty, invoked with
40
+ # `--result-path=<path>` after step 6 finalizes the per-run result JSON
41
+ # (before the terminal health-check step). Useful for downstream
42
+ # distribution, notification, or pipeline chaining. Failures are logged to
43
+ # workflow_warnings[] but never fail the workflow. Empty = no-op (default).
44
+
45
+ on_complete = ""
46
+
37
47
  # --- Optional asset overrides ---
38
48
  #
39
49
  # Lift the canonical asset paths so orgs can substitute house-style copies
@@ -41,9 +51,3 @@ persistent_facts = [
41
51
 
42
52
  managed_section_format_path = ""
43
53
  snippet_format_path = ""
44
-
45
- # Override the manifest location. Empty = use
46
- # {skills_output_folder}/.export-manifest.json. Useful for orgs that want
47
- # a cross-project manifest at a stable path (e.g. ~/.skf/export-manifest.json).
48
-
49
- export_manifest_path = ""