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
@@ -7,7 +7,7 @@ description: Drop a specific skill version or an entire skill — soft (deprecat
7
7
 
8
8
  ## Overview
9
9
 
10
- Drops a specific skill version or an entire skill, either as a soft deprecation (manifest-only, files retained) or a hard purge (files deleted). Ensures platform context files are rebuilt to exclude dropped versions. Every destructive action requires explicit user confirmation — nothing is deleted silently. The export manifest is the source of truth; the filesystem is updated to match.
10
+ Drops a specific skill version or an entire skill, either as a soft deprecation (manifest-only, files retained) or a hard purge (files deleted). Ensures platform context files are rebuilt to exclude dropped versions. In interactive mode every destructive action requires explicit user confirmation — nothing is deleted silently; headless runs auto-resolve the gates with their default action and log each auto-decision. The export manifest is the source of truth; the filesystem is updated to match.
11
11
 
12
12
  ## Conventions
13
13
 
@@ -21,13 +21,13 @@ Drops a specific skill version or an entire skill, either as a soft deprecation
21
21
 
22
22
  ## Role
23
23
 
24
- You are Ferris in Management mode — a destructive operation specialist who enforces safety guards. You treat every drop as potentially irreversible and require explicit confirmation before touching the manifest or filesystem. You protect the active version, keep the export manifest consistent with on-disk state, and ensure downstream platform context files are rebuilt.
24
+ You are Ferris in Management mode — a destructive operation specialist who enforces safety guards. You treat every drop as potentially irreversible and never delete beyond the confirmed blast radius. You protect the active version, keep the export manifest consistent with on-disk state, and ensure downstream platform context files are rebuilt.
25
25
 
26
26
  ## Workflow Rules
27
27
 
28
28
  These rules apply to every step in this workflow:
29
29
 
30
- - Never delete files without explicit user confirmation in purge mode
30
+ - Never delete files in purge mode without clearing the §10 confirmation gate (auto-resolved with its default in headless)
31
31
  - Never drop an active version when other non-deprecated versions exist — enforce the active version guard
32
32
  - Only load one step file at a time — never preload future steps
33
33
  - If any instruction references a subprocess or tool you lack, achieve the outcome in your main context thread
@@ -52,31 +52,25 @@ These rules apply to every step in this workflow:
52
52
  | **Flags** | `--headless` / `-H` (auto-resolve all gates); `--dry-run` (run selection + display the §10 confirmation block, then exit with `status="dry-run"` — no manifest mutation, no file deletion). Useful for "show me what this would touch before I commit." |
53
53
  | **Gates** | step 1: Input Gate [use args], Confirm Gate [Y] |
54
54
  | **Outputs** | Updated manifest, rebuilt context files, (purge: deleted directories), `drop-skill-result-{timestamp}.json` and `drop-skill-result-latest.json` |
55
- | **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. When `forbid_purge_in_headless` is `"true"` in `customize.toml` AND `drop_mode = "purge"`, On-Activation §4 HALTs with exit code 6 (`halt_reason: "headless-purge-forbidden"`) before any work begins. |
55
+ | **Headless** | Gates auto-resolve with their default action (see Workflow Rules). When `forbid_purge_in_headless` is `"true"` in `customize.toml` AND the effective drop mode is `"purge"` (defined in On-Activation §4 — explicit `mode=purge` or `default_mode` purge), §4 HALTs with exit code 6 (`halt_reason: "headless-purge-forbidden"`) before any work begins. |
56
56
  | **Exit codes** | See "Exit Codes" below |
57
57
 
58
58
  ## Exit Codes
59
59
 
60
- Every HARD HALT in this workflow exits with a stable code so headless automators can branch on the failure class without grepping message text:
60
+ Every hard HALT exits with a stable code so headless automators branch on the failure class without grepping message text. The `Raised by` column names the HALT class per code; the authoritative per-site declarations (with exact `halt_reason`) live in the step files.
61
61
 
62
- | Code | Meaning | Raised by |
63
- | ---- | -------------------- | -------------------------------------------------------------------------------------------- |
64
- | 0 | success | step 4 (terminal) |
65
- | 2 | input-missing / input-invalid | step 1 §4 (headless missing `skill_name` arg) `input-missing`; step 1 §4 (headless skill arg matches no listed skill) / step 1 §6 (headless version arg absent from the `versions` map) → `input-invalid` |
66
- | 3 | resolution-failure | step 1 §2 (manifest is malformed JSON); step 1 §3 (no skills found anywhere) |
67
- | 4 | write-failure | On-Activation §3 pre-flight write probe (skills_output_folder unwritable); step 2 §2 (manifest write `manifest-write-failed`); step 2 §3 (context-file rewrite → `context-rebuild-failed`); step 2 §4 (full purge failure — every target directory failed to delete → `delete-failed`) |
68
- | 5 | state-conflict | step 1 §7 (active-version-guard refuses to drop the active version while non-deprecated peers remain) |
69
- | 6 | user-cancelled | step 1 §10 confirmation gate `[N]`; any prompt that accepted `cancel`/`exit`/`:q`; On-Activation §4 (`headless-purge-forbidden`) |
62
+ | Code | Meaning | Raised by (class) |
63
+ | ---- | -------------------- | ----------------- |
64
+ | 0 | success | step 4 (terminal) |
65
+ | 2 | input-missing / input-invalid | step 1 headless input gates — missing or unmatched `skill_name`, `version`, or `--mode` value (§4 / §6 / §8) |
66
+ | 3 | resolution-failure | step 1 manifest/skill-list resolution (§2 corrupt manifest, §3 nothing to drop) |
67
+ | 4 | write-failure | On-Activation write probe; step 2 manifest write / context rebuild / full-purge failure |
68
+ | 5 | state-conflict | step 1 active-version guard (§7) |
69
+ | 6 | user-cancelled | any interactive cancel or confirm-gate `[N]`; On-Activation headless-purge guard |
70
70
 
71
71
  ## Result Contract (Headless)
72
72
 
73
- When `{headless_mode}` is true, step 3 emits a single-line JSON envelope on **stdout** before chaining to step 4, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
74
-
75
- ```
76
- SKF_DROP_SKILL_RESULT_JSON: {"status":"success|error|dry-run","skill":"…|null","drop_mode":"…|null","versions_affected":[],"files_deleted":[],"manifest_updated":false,"exit_code":0,"halt_reason":null}
77
- ```
78
-
79
- `status` is `"success"` on the terminal happy path, `"dry-run"` when `--dry-run` was set and the workflow exited before §11 stores decisions, `"error"` on any HALT. `halt_reason` is one of: `null` (success), `"input-missing"`, `"input-invalid"`, `"manifest-corrupt"`, `"nothing-to-drop"`, `"active-version-guard-refused"`, `"headless-purge-forbidden"`, `"manifest-write-failed"`, `"context-rebuild-failed"`, `"delete-failed"`, `"write-failed"`, `"user-cancelled"`. `exit_code` matches the table above.
73
+ When `{headless_mode}` is true, step 3 emits a single-line `SKF_DROP_SKILL_RESULT_JSON:` envelope on **stdout**; every HARD HALT emits the same shape on **stderr** with `status: "error"`. The template, `status`/`halt_reason` semantics, `exit_code` (per the Exit Codes table above), and full enum live in `references/headless-contract.md` — the emitting stages load it directly, so an error path never depends on this file.
80
74
 
81
75
  ## On Activation
82
76
 
@@ -103,14 +97,17 @@ SKF_DROP_SKILL_RESULT_JSON: {"status":"success|error|dry-run","skill":"…|null"
103
97
 
104
98
  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 scalar.
105
99
 
106
- Apply the scalar fallback now so stage files don't have to repeat the conditional logic. For each of the four scalars, if the merged value is empty or absent, the bundled default applies:
100
+ Apply the scalar fallback now so stage files don't have to repeat the conditional logic. For each of the five scalars, if the merged value is empty or absent, the bundled default applies:
107
101
 
108
102
  - `{defaultMode}` ← `workflow.default_mode` (empty = always prompt; `"deprecate"` or `"purge"` = skip §8 Ask Mode)
109
103
  - `{forbidPurgeInHeadless}` ← `workflow.forbid_purge_in_headless` (empty or non-`"true"` = no guard)
110
104
  - `{unknownIdeDefaultContextFile}` ← `workflow.unknown_ide_default_context_file` if non-empty, else `AGENTS.md`
111
105
  - `{unknownIdeDefaultSkillRoot}` ← `workflow.unknown_ide_default_skill_root` if non-empty, else `.agents/skills/`
106
+ - `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty (no-op — step 3 skips the post-drop hook entirely)
107
+
108
+ Stash all five as workflow-context variables. Stage files reference them directly — no conditional at the usage site.
112
109
 
113
- Stash all four as workflow-context variables. Stage files reference them directly no conditional at the usage site.
110
+ Also apply the array surfaces: run `workflow.activation_steps_prepend` now, keep `workflow.persistent_facts` as standing context (`file:` entries load their contents), then run `workflow.activation_steps_append` after.
114
111
 
115
112
  4. **Pre-flight write probe + headless-purge guard.**
116
113
 
@@ -124,6 +121,6 @@ SKF_DROP_SKILL_RESULT_JSON: {"status":"success|error|dry-run","skill":"…|null"
124
121
 
125
122
  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 `skill: null` and `drop_mode: null` (neither is resolved yet at activation time).
126
123
 
127
- Second, enforce the headless-purge guard. If `{headless_mode}` is true AND `{forbidPurgeInHeadless}` is `"true"` AND the parsed `mode` arg is `"purge"`: HALT with exit code 6 and `halt_reason: "headless-purge-forbidden"`, emit the error envelope, and exit immediately. The operator must re-run with `mode=deprecate` or set `forbid_purge_in_headless = ""` (or omit the override entirely) to proceed.
124
+ Second, enforce the headless-purge guard. First compute the **effective drop mode**: it is `"purge"` when the parsed `mode` arg is `"purge"`, OR when no `mode` arg was passed AND `{defaultMode}` (resolved in §3) is `"purge"` — a purge reached via `default_mode` is still an unattended irreversible purge and must be caught here, not only an explicit `--mode purge`. If `{headless_mode}` is true AND `{forbidPurgeInHeadless}` is `"true"` AND the effective drop mode is `"purge"`: HALT with exit code 6 and `halt_reason: "headless-purge-forbidden"`, emit the error envelope, and exit immediately. The operator must re-run with an explicit `mode=deprecate` (an explicit arg overrides `default_mode`) or set `forbid_purge_in_headless = ""` (or omit the override entirely) to proceed.
128
125
 
129
126
  5. Load, read the full file, and then execute `references/select.md` to begin the workflow.
@@ -1,6 +1,8 @@
1
1
  # DO NOT EDIT -- overwritten on every update.
2
2
  #
3
3
  # Workflow customization surface for skf-drop-skill.
4
+ # Team overrides: {project-root}/_bmad/custom/skf-drop-skill.toml
5
+ # Personal overrides: {project-root}/_bmad/custom/skf-drop-skill.user.toml
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-drop hook. When non-empty, invoked with
40
+ # `--result-path=<path>` after the drop-skill result JSON is finalized
41
+ # (references/report.md). Failures are logged to workflow_warnings[] but
42
+ # never fail the workflow — useful for audit-log emit, a notifier, or a
43
+ # ticket-tracker integration after an irreversible purge.
44
+
45
+ on_complete = ""
46
+
37
47
  # --- Optional safety + default scalars ---
38
48
  #
39
49
  # Default drop mode. Empty string = always prompt the user at §8 Ask Mode.
@@ -18,6 +18,23 @@ manifestOpsProbeOrder:
18
18
  rebuildManagedSectionsProbeOrder:
19
19
  - '{project-root}/_bmad/skf/shared/scripts/skf-rebuild-managed-sections.py'
20
20
  - '{project-root}/src/shared/scripts/skf-rebuild-managed-sections.py'
21
+ # Resolve `{updateActiveSymlinkHelper}` similarly. §4 uses it (update
22
+ # action) to atomically repoint `{skill_group}/active` after a version-level
23
+ # purge deletes the version the symlink pointed at — the helper does a
24
+ # temp-symlink + os.replace flip so concurrent readers never see a missing
25
+ # link. Matches skf-update-skill/references/write.md and
26
+ # skf-rename-skill/references/execute.md. HALT if neither candidate exists.
27
+ updateActiveSymlinkProbeOrder:
28
+ - '{project-root}/_bmad/skf/shared/scripts/skf-update-active-symlink.py'
29
+ - '{project-root}/src/shared/scripts/skf-update-active-symlink.py'
30
+ # Standalone single-line result-envelope contract emitted at every headless
31
+ # HALT in this step. Loaded in §1 so no error path depends on SKILL.md
32
+ # remaining in context under compaction.
33
+ headlessContract: 'headless-contract.md'
34
+ # Deterministic recursive byte sizing + human formatting for `disk_freed`
35
+ # (§4). Bundled with this skill; the same helper backs select.md §9b's
36
+ # blast-radius preview, so gate and report agree on the method.
37
+ dirSizesHelper: 'scripts/dir-sizes.py'
21
38
  ---
22
39
 
23
40
  <!-- Config: communicate in {communication_language}. -->
@@ -43,6 +60,8 @@ Read `{versionPathsKnowledge}` again and confirm the templates and management op
43
60
 
44
61
  Also read `{managedSectionLogic}` for the format template, the four-case logic, and the skill index rebuild rules that will be reused in section 3.
45
62
 
63
+ If `{headless_mode}` is true, also read `{headlessContract}` now — it defines the single-line result envelope the error paths below emit, so the shape is in context even if SKILL.md was compacted out.
64
+
46
65
  ### 2. Update Export Manifest
47
66
 
48
67
  **If `target_in_manifest == false`** (draft skill discovered only by on-disk scan): Skip this section entirely. There is no manifest entry to deprecate or delete. Set `manifest_updated = false` and proceed directly to section 3. Step-01 forced `drop_mode = "purge"` and `is_skill_level = true` in this case, so the subsequent sections will hard-delete the on-disk directories without any manifest interaction.
@@ -75,7 +94,7 @@ Set context flag `manifest_updated = true`.
75
94
 
76
95
  - Do not proceed to section 3
77
96
  - Report: "**Manifest update failed:** {captured stderr}. No files were deleted and platform context files were not rebuilt. The manifest is in its pre-drop state — rerun the workflow once the underlying issue is resolved."
78
- - Store `manifest_updated = false` and jump to section 6. In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with `halt_reason: "manifest-write-failed"` and exit code 4.
97
+ - Store `manifest_updated = false` and jump to section 6. In headless mode, emit the error envelope per `{headlessContract}` with `halt_reason: "manifest-write-failed"` and exit code 4.
79
98
 
80
99
  ### 3. Rebuild Context Files
81
100
 
@@ -84,9 +103,9 @@ Load the `ides` list from `config.yaml`. The installer writes IDE identifiers
84
103
  **Resolve `target_context_files`** using the canonical mapping table in `{managedSectionLogic}`:
85
104
 
86
105
  1. For each entry in `config.yaml.ides`, look up its `context_file` and `skill_root` from the mapping table
87
- 2. For any entry not found in the table, default to AGENTS.md / `.agents/skills/` and emit a warning: "Unknown IDE '{value}' in config.yaml — defaulting to AGENTS.md"
106
+ 2. For any entry not found in the table, default to `{unknownIdeDefaultContextFile}` / `{unknownIdeDefaultSkillRoot}` (resolved at SKILL.md On Activation §3 from `workflow.unknown_ide_default_*`, bundled defaults `AGENTS.md` / `.agents/skills/`) and emit a warning: "Unknown IDE '{value}' in config.yaml — defaulting to {unknownIdeDefaultContextFile}"
88
107
  3. Deduplicate by `context_file` — when multiple IDEs map to the same context file, use the first configured IDE's `skill_root`
89
- 4. If `config.yaml.ides` is absent or the mapping yields an empty list, fall back to `[{context_file: "AGENTS.md", skill_root: ".agents/skills/"}]` and emit a note: "No IDEs configured in config.yaml — defaulting to AGENTS.md"
108
+ 4. If `config.yaml.ides` is absent or the mapping yields an empty list, fall back to `[{context_file: "{unknownIdeDefaultContextFile}", skill_root: "{unknownIdeDefaultSkillRoot}"}]` and emit a note: "No IDEs configured in config.yaml — defaulting to {unknownIdeDefaultContextFile}"
90
109
 
91
110
  Store the result as `target_context_files` for this section.
92
111
 
@@ -156,28 +175,45 @@ Report: "**Rebuilt managed sections in:** {list of updated files}. {if any faile
156
175
 
157
176
  **If `drop_mode == "purge"`:**
158
177
 
159
- 1. Initialize `files_deleted = []` and `bytes_freed = 0`.
178
+ 1. Initialize `files_deleted = []` and `delete_failures = []` (paths whose deletion was attempted but did not succeed).
179
+
180
+ 2. **Measure sizes before deleting anything.** Run the sizing helper once over `affected_directories` so each path's byte size is captured while it still exists:
160
181
 
161
- 2. Also initialize `delete_failures = []` to track paths whose deletion was attempted but did not succeed.
182
+ ```bash
183
+ uv run {dirSizesHelper} sizes {each path in affected_directories, space-separated}
184
+ ```
185
+
186
+ Keep each `result.paths[].bytes` as `path_bytes[{path}]`; a path reported `exists: false` is already gone. If the helper is unavailable, fall back to `du -sb` per existing path.
162
187
 
163
188
  3. For each directory path in `affected_directories`:
164
189
  a. Verify the path is inside either `{skills_output_folder}` or `{forge_data_folder}` (defense in depth against accidental deletion of unrelated paths)
165
190
  b. If the directory does not exist, record it as "(already absent)" and continue
166
- c. Compute the directory size in bytes before deletion (recursive sum)
167
- d. Delete the directory recursively
168
- e. Verify deletion succeeded (the path no longer exists)
169
- f. Append the path to `files_deleted` and add its byte size to `bytes_freed`
191
+ c. Delete the directory recursively
192
+ d. Verify deletion succeeded (the path no longer exists)
193
+ e. Append the path to `files_deleted`
170
194
 
171
195
  4. **Version-level purge, single version:**
172
196
  - `{skills_output_folder}/{target_skill}/{version}/` is deleted, but `{skills_output_folder}/{target_skill}/` remains (it still contains other versions or the `active` symlink)
173
- - If the `active` symlink pointed to the just-deleted version, update or remove it:
174
- - If other versions remain in the manifest for `{target_skill}`, repoint `active` to the manifest's current `active_version` (skipping deprecated)
175
- - If no non-deprecated versions remain, remove the `active` symlink (reachable only when dropping the sole surviving version, which in step 1 was permitted because no other non-deprecated versions existed)
197
+ - If the `active` symlink pointed to the just-deleted version, update or remove it. The version directory is already gone at this point, so a symlink problem never claims `delete-failed` — record it and continue (`verification_errors`) so the report surfaces the manual repair rather than masking a successful purge.
198
+ - **Other non-deprecated versions remain** for `{target_skill}`: resolve `{new_active_version}` = the version the manifest now lists as `active_version` for `{target_skill}` (if that one is deprecated, the newest non-deprecated version in its `versions` map). Repoint `active` to it atomically through the shared helper rather than a hand-rolled `ln` — resolve `{updateActiveSymlinkHelper}` from `{updateActiveSymlinkProbeOrder}` (first existing path wins), then:
199
+ ```bash
200
+ python3 {updateActiveSymlinkHelper} update \
201
+ --skill-group {skills_output_folder}/{target_skill} \
202
+ --version {new_active_version}
203
+ ```
204
+ The helper does a temp-symlink + `os.replace` flip, so a concurrent reader never sees a missing `active`. Record a `mismatch`/`missing-target` exit (code 2), or a missing helper (no probe candidate), in `verification_errors` with the manual fix (`ln -sfn {new_active_version} {skills_output_folder}/{target_skill}/active`) and continue.
205
+ - **No non-deprecated versions remain** (reachable only when dropping the sole surviving version, permitted in step 1 because no other non-deprecated versions existed): remove the now-dangling `active` symlink with a single atomic unlink of the link itself — `rm {skills_output_folder}/{target_skill}/active` (unlink removes only the symlink, never its target, and is atomic). A single unlink has one correct outcome and no intermediate state, so it stays in-prompt (the helper has no removal action).
176
206
 
177
207
  5. **Skill-level purge:**
178
208
  - `{skills_output_folder}/{target_skill}/` and `{forge_data_folder}/{target_skill}/` are deleted in full — the `active` symlink disappears with the parent directory
179
209
 
180
- 6. Convert `bytes_freed` to a human-readable string for the final report (e.g. `"4.2 MB"`). Store as `disk_freed`.
210
+ 6. Sum the sizes of the paths in `files_deleted` and format one human-readable label through the helper do not add or round in-prompt:
211
+
212
+ ```bash
213
+ uv run {dirSizesHelper} humanize {path_bytes[p] for each p in files_deleted, space-separated}
214
+ ```
215
+
216
+ Store `result.total_human` as `disk_freed` (e.g. `"4.2 MB"`; `"0 B"` when nothing was deleted).
181
217
 
182
218
  **On deletion error (per path):**
183
219
 
@@ -187,7 +223,7 @@ Report: "**Rebuilt managed sections in:** {list of updated files}. {if any faile
187
223
 
188
224
  **After the loop — classify the deletion outcome.** Let `attempted` be the number of paths in `affected_directories` that existed on disk (i.e. were not recorded as "(already absent)"):
189
225
 
190
- - **Full purge failure** — `attempted > 0` AND every attempted path is in `delete_failures` (nothing was deleted): the purge accomplished none of its destructive intent, so it must NOT report success. HALT (exit code 4, `halt_reason: "delete-failed"`): "**Purge failed** — none of the {attempted} target director(ies) could be deleted: {list each path with its error}. The manifest and context files were already updated in sections 2–3; the on-disk files remain and can be removed manually (`rm -rf {path}`)." In headless mode, emit the error envelope per SKILL.md "Result Contract (Headless)" with the resolved `skill`, `drop_mode`, `versions_affected`, `files_deleted: []`, and `manifest_updated` from section 2. Do not proceed to section 5.
226
+ - **Full purge failure** — `attempted > 0` AND every attempted path is in `delete_failures` (nothing was deleted): the purge accomplished none of its destructive intent, so it must NOT report success. HALT (exit code 4, `halt_reason: "delete-failed"`): "**Purge failed** — none of the {attempted} target director(ies) could be deleted: {list each path with its error}. The manifest and context files were already updated in sections 2–3; the on-disk files remain and can be removed manually (`rm -rf {path}`)." In headless mode, emit the error envelope per `{headlessContract}` with the resolved `skill`, `drop_mode`, `versions_affected`, `files_deleted: []`, and `manifest_updated` from section 2. Do not proceed to section 5.
191
227
  - **Partial purge failure** — `delete_failures` is non-empty but at least one path was deleted: keep record-and-continue. Set `purge_status = "partial"` so step 3's on-disk result record reflects it (the `output-contract-schema.md` `status` enum supports `"partial"`); proceed to section 5. The headless single-line envelope has no `"partial"` value in its enum, so it stays `"success"` while `context_files_failed`/`verification_errors`/the report surface the unfreed paths.
192
228
  - **No failures** — `delete_failures` is empty: set `purge_status = "success"` and proceed to section 5.
193
229
 
@@ -220,21 +256,5 @@ Store the following for step 3:
220
256
 
221
257
  ### 7. Load Next Step
222
258
 
223
- Load, read the full file, and then execute `{nextStepFile}`.
224
-
225
- ## Error Handling Summary
226
-
227
- If any stage fails, record which stage failed and provide recovery guidance in the final report:
228
-
229
- | Failed Stage | Recovery Guidance |
230
- |--------------|-------------------|
231
- | Manifest update | "Manifest is in pre-drop state. Re-run the workflow once the underlying I/O issue is resolved. No files were deleted." |
232
- | Context file rebuild | "Manifest is already updated. Re-run `[EX] Export Skill` against any still-valid skill to regenerate the affected managed sections, or rerun the drop workflow." |
233
- | File deletion (purge — partial) | "Manifest and context files are consistent. Remaining directories listed in the report can be deleted manually: `rm -rf {path}`." (status `partial`) |
234
- | File deletion (purge — full) | "No target directory could be deleted. Manifest and context files are already updated; the on-disk files remain. HALT with `delete-failed` (exit 4) — re-run once the I/O issue is resolved, or remove the listed directories manually." |
235
- | Verification | "Execution completed but post-write checks found drift. See the report for specific paths requiring manual review." |
236
-
237
- ## CRITICAL STEP COMPLETION NOTE
238
-
239
- ONLY WHEN all execution stages have been attempted (manifest update, context rebuild, file deletion in purge mode, verification) and results have been stored in context, will you then load and read fully `{nextStepFile}` to generate the final report.
259
+ The report in `{nextStepFile}` renders from the results stored in §6, so chain to it only after every execution stage above has been attempted and its outcome stored. Load, read the full file, and then execute it.
240
260
 
@@ -0,0 +1,18 @@
1
+ # Headless Result Envelope
2
+
3
+ The single-line JSON contract every headless run of skf-drop-skill emits. It stands alone: the stages that emit it load this file directly, so an error path never depends on any other file staying in context under compaction.
4
+
5
+ When `{headless_mode}` is true, step 3 emits this envelope on **stdout** before chaining to step 4; every HARD HALT emits the same shape on **stderr** with `status: "error"`:
6
+
7
+ ```
8
+ SKF_DROP_SKILL_RESULT_JSON: {"status":"success|error|dry-run","skill":"…|null","drop_mode":"…|null","versions_affected":[],"files_deleted":[],"manifest_updated":false,"exit_code":0,"halt_reason":null}
9
+ ```
10
+
11
+ Field rules:
12
+
13
+ - `status` — `"success"` on the terminal happy path, `"dry-run"` when `--dry-run` was set and the workflow exited at the confirmation gate before any mutation, `"error"` on any HALT.
14
+ - `halt_reason` — `null` on success/dry-run; otherwise one of `"input-missing"`, `"input-invalid"`, `"manifest-corrupt"`, `"nothing-to-drop"`, `"active-version-guard-refused"`, `"headless-purge-forbidden"`, `"manifest-write-failed"`, `"context-rebuild-failed"`, `"delete-failed"`, `"write-failed"`, `"user-cancelled"`.
15
+ - `exit_code` — the stable numeric code the emitting HALT specifies (`0` on success/dry-run).
16
+ - `skill` / `drop_mode` / `versions_affected` / `files_deleted` / `manifest_updated` — the discriminating values the emit site supplies; any key not yet resolved at that point takes the default shown in the template (`null`, `[]`, or `false`).
17
+
18
+ Each emit site supplies its own `exit_code`, `halt_reason`, and whichever discriminating fields are known at that point; all other keys default as shown.
@@ -9,16 +9,4 @@ nextStepFile: 'shared/health-check.md'
9
9
 
10
10
  # Step 4: Workflow Health Check
11
11
 
12
- ## STEP GOAL:
13
-
14
- Chain to the shared workflow self-improvement health check at `{nextStepFile}`. This is the terminal step of drop-skill — after the shared health check completes, the workflow is fully done.
15
-
16
- ## Rules
17
-
18
- - No user-facing reports, file writes, or result contracts in this step — those belong in step 3
19
- - Delegate directly to `{nextStepFile}` with no additional commentary
20
- - Do not attempt any other action between loading this step and executing `{nextStepFile}`
21
-
22
- ## MANDATORY SEQUENCE
23
-
24
- Load `{nextStepFile}`, read it fully, then execute it.
12
+ This is the terminal step of drop-skill. Load `{nextStepFile}`, read it fully, then execute it — do nothing else here (no user-facing reports, file writes, or result contracts; those were step 3). After the shared health check completes, the workflow is done.
@@ -1,5 +1,14 @@
1
1
  ---
2
2
  nextStepFile: 'health-check.md'
3
+ # §1 resolves `{manifestOpsHelper}` from this order (installed SKF module path
4
+ # first, src/ dev-checkout fallback) to read the target skill's remaining
5
+ # versions — `get` for the versions+status map and `affected-versions` for the
6
+ # numeric semver-descending order — instead of re-parsing the manifest by hand.
7
+ # A read, not an atomicity-critical write: if neither path resolves, §1 reads
8
+ # the manifest in-prompt.
9
+ manifestOpsProbeOrder:
10
+ - '{project-root}/_bmad/skf/shared/scripts/skf-manifest-ops.py'
11
+ - '{project-root}/src/shared/scripts/skf-manifest-ops.py'
3
12
  ---
4
13
 
5
14
  <!-- Config: communicate in {communication_language}. Render the report block in {document_output_language}. -->
@@ -14,7 +23,7 @@ Present a clear, final summary of what the drop workflow changed — manifest st
14
23
 
15
24
  - Focus only on reporting results stored in context by step 2 — do not re-execute any part of the drop
16
25
  - Do not hide verification errors or failed context file rebuilds
17
- - Chains to the local health-check step via `{nextStepFile}` after completion the user-facing report is NOT the terminal step
26
+ - Chains to the local health-check step via `{nextStepFile}` after completion (see §3)
18
27
 
19
28
  ## MANDATORY SEQUENCE
20
29
 
@@ -26,14 +35,23 @@ Set `remaining_versions_display = "(skill fully removed)"`.
26
35
 
27
36
  **If `is_skill_level == false`:**
28
37
 
29
- Read `{skills_output_folder}/.export-manifest.json` and look up `exports.{target_skill}.versions`. Build a human-readable list of every remaining version with its status, with the active one marked:
38
+ **Resolve `{manifestOpsHelper}`** first existing path in `{manifestOpsProbeOrder}`, then read the target skill's remaining versions through it rather than re-parsing the manifest by hand:
39
+
40
+ ```bash
41
+ python3 {manifestOpsHelper} {skills_output_folder} get {target_skill}
42
+ python3 {manifestOpsHelper} {skills_output_folder} affected-versions {target_skill}
43
+ ```
44
+
45
+ `get` returns `result.entry` (its `active_version` and its `versions` map with each version's `status`); `affected-versions` returns `result.affected_versions` sorted numerically-descending (so `0.10.0` precedes `0.9.0`). Build the display in that order, annotating each version with its `status` and marking `active_version` with a trailing `*`:
30
46
 
31
47
  ```
32
- - 0.1.0 (deprecated)
33
- - 0.5.0 (archived)
34
48
  - 0.6.0 (active) *
49
+ - 0.5.0 (archived)
50
+ - 0.1.0 (deprecated)
35
51
  ```
36
52
 
53
+ **If neither `{manifestOpsProbeOrder}` candidate resolves:** read `exports.{target_skill}.versions` from `{skills_output_folder}/.export-manifest.json` in-prompt, list each remaining version with its `status` (active marked `*`), ordering newest-first by comparing version components numerically.
54
+
37
55
  ### 2. Render the Report
38
56
 
39
57
  Display the following block, filling in values from context:
@@ -71,11 +89,19 @@ These require manual review — see the error-handling guidance in step 2.
71
89
 
72
90
  ### Result Contract
73
91
 
74
- Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{skills_output_folder}/drop-skill-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include all purged file paths in `outputs`; include `target_skill`, `drop_mode`, and `versions_affected` in `summary`.
92
+ Write the result contract per `shared/references/output-contract-schema.md`: the per-run record at `{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json` (UTC timestamp, resolution to seconds) and a copy at `{skills_output_folder}/drop-skill-result-latest.json` (stable path for pipeline consumers — copy, not symlink). Include all purged file paths in `outputs`; include `target_skill`, `drop_mode`, `versions_affected`, and a `headless_provenance` object in `summary`.
93
+
94
+ `headless_provenance` persists the §8/§10 decision trail from step 1 so an unattended run's auto-decisions survive in the durable record, not only in the transient log — a consumer can tell an operator-confirmed drop from a headless auto-confirmed one without re-deriving it:
95
+
96
+ ```json
97
+ "headless_provenance": {"headless": {headless_mode}, "mode_source": "{mode_source}", "confirm": "{confirm_source}"}
98
+ ```
99
+
100
+ where `{headless_mode}` is the resolved boolean, `{mode_source}` is the step-1 §8 value (`"--mode argument"` / `"customize.toml.workflow.default_mode"` / `"interactive-prompt"` / `"draft-skill-forced-purge"`), and `{confirm_source}` is the step-1 §10 value (`"headless-auto"` / `"user-explicit"`).
75
101
 
76
102
  Set the record's `status` from step 2's `purge_status`: `"partial"` when some (but not all) purge-mode directories failed to delete — surface the failing paths from `delete_failures` in `summary.delete_failures` — otherwise `"success"`. A *full* purge failure never reaches this step: step 2 §4 HALTs with `halt_reason: "delete-failed"` and the error-envelope path below handles it.
77
103
 
78
- When `{headless_mode}` is true, also emit the single-line envelope on **stdout** before chaining to step 4 (matches the SKILL.md "Result Contract (Headless)" shape):
104
+ When `{headless_mode}` is true, also emit the single-line envelope on **stdout** before chaining to step 4 (the full shape and field rules are in `references/headless-contract.md`):
79
105
 
80
106
  ```
81
107
  SKF_DROP_SKILL_RESULT_JSON: {"status":"success","skill":"{target_skill}","drop_mode":"{drop_mode}","versions_affected":{target_versions},"files_deleted":{files_deleted},"manifest_updated":{manifest_updated},"exit_code":0,"halt_reason":null}
@@ -83,11 +109,17 @@ SKF_DROP_SKILL_RESULT_JSON: {"status":"success","skill":"{target_skill}","drop_m
83
109
 
84
110
  Substitute `{target_versions}` as a JSON array (e.g. `["0.5.0"]`) or the literal string `"all"`; substitute `{files_deleted}` as a JSON array of absolute paths (`[]` in soft-drop mode); `manifest_updated` is the boolean from step 2's context.
85
111
 
86
- ### 3. Chain to Health Check
112
+ ### Post-drop hook (optional)
87
113
 
88
- ONLY WHEN the report has been rendered and the result contract saved will you then load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step do not stop here even though the report reads as final.
114
+ If `{onCompleteCommand}` is non-empty (resolved at SKILL.md On Activation §3 from `workflow.on_complete`), invoke it once the result contract above is finalized:
89
115
 
90
- ## CRITICAL STEP COMPLETION NOTE
116
+ ```bash
117
+ {onCompleteCommand} --result-path={result_json_path}
118
+ ```
119
+
120
+ where `{result_json_path}` is the per-run record written above (`{skills_output_folder}/drop-skill-result-{YYYYMMDD-HHmmss}.json`). Log success or failure to `workflow_warnings[]` — never fail the workflow on a hook error; the drop has already completed and may be irreversible. When `{onCompleteCommand}` is empty (bundled default), skip the invocation entirely.
121
+
122
+ ### 3. Chain to Health Check
91
123
 
92
- This step chains to the local health-check step (`{nextStepFile}`), which in turn delegates to `shared/health-check.md`. After the health check completes, the drop-skill workflow is fully done. Do not re-run any earlier step automatically if the user wants another drop, they should re-invoke the workflow from the top.
124
+ ONLY WHEN the report has been rendered and the result contract saved will you then load, read the full file, and execute `{nextStepFile}`. The health-check step is the true terminal step — do not stop here even though the report reads as final, and do not re-run any earlier step after it completes (a fresh drop means re-invoking the workflow from the top).
93
125