bmad-module-skill-forge 1.9.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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/README.md +10 -5
- package/docs/_data/pinned.yaml +2 -2
- package/docs/agents.md +11 -2
- package/docs/architecture.md +5 -4
- package/docs/bmad-synergy.md +30 -7
- package/docs/campaign.md +172 -0
- package/docs/examples.md +7 -3
- package/docs/forge-auto.md +90 -0
- package/docs/getting-started.md +9 -0
- package/docs/how-it-works.md +6 -4
- package/docs/index.md +4 -3
- package/docs/skill-model.md +1 -1
- package/docs/troubleshooting.md +17 -1
- package/docs/verifying-a-skill.md +2 -2
- package/docs/why-skf.md +1 -1
- package/docs/workflows.md +64 -24
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +4 -3
- package/src/shared/data/language-corpora.json +32 -0
- package/src/shared/references/pipeline-contracts.md +6 -3
- package/src/shared/scripts/schemas/skill-brief.v1.json +6 -1
- package/src/shared/scripts/skf-comention-pairs.py +350 -0
- package/src/shared/scripts/skf-count-tokens.py +222 -0
- package/src/shared/scripts/skf-derive-assembly-shape.py +107 -0
- package/src/shared/scripts/skf-detect-docs.py +240 -4
- package/src/shared/scripts/skf-detect-language.py +82 -3
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-emit-result-envelope.py +17 -0
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- package/src/shared/scripts/skf-extract-public-api.py +53 -0
- package/src/shared/scripts/skf-find-cycles.py +246 -0
- package/src/shared/scripts/skf-hash-content.py +487 -2
- package/src/shared/scripts/skf-language-corpora.py +100 -0
- package/src/shared/scripts/skf-manifest-ops.py +67 -1
- package/src/shared/scripts/skf-merge-doc-urls.py +244 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-rebuild-managed-sections.py +180 -0
- package/src/shared/scripts/skf-recommend-scope-type.py +1 -2
- package/src/shared/scripts/skf-render-metadata-stats.py +499 -0
- package/src/shared/scripts/skf-rewrite-skill-name.py +308 -0
- package/src/shared/scripts/skf-shape-detect.py +576 -25
- package/src/shared/scripts/skf-shard-body.py +461 -0
- package/src/shared/scripts/skf-skill-inventory.py +139 -3
- package/src/shared/scripts/skf-structural-diff.py +307 -67
- package/src/shared/scripts/skf-validate-brief-schema.py +41 -6
- package/src/shared/scripts/skf-validate-feasibility-report.py +234 -0
- package/src/shared/scripts/skf-validate-frontmatter.py +17 -12
- package/src/shared/scripts/skf-validate-output.py +443 -10
- package/src/shared/scripts/skf-validate-pins.py +3 -3
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/shared/scripts/skf-write-skill-brief.py +21 -5
- package/src/skf-analyze-source/SKILL.md +17 -32
- package/src/skf-analyze-source/assets/skill-brief-schema.md +1 -1
- package/src/skf-analyze-source/customize.toml +2 -0
- package/src/skf-analyze-source/references/auto-docs-only.md +116 -0
- package/src/skf-analyze-source/references/continue.md +8 -13
- package/src/skf-analyze-source/references/generate-briefs.md +28 -12
- package/src/skf-analyze-source/references/headless-contract.md +33 -0
- package/src/skf-analyze-source/references/health-check.md +1 -2
- package/src/skf-analyze-source/references/identify-units.md +25 -36
- package/src/skf-analyze-source/references/init.md +12 -25
- package/src/skf-analyze-source/references/map-and-detect.md +8 -20
- package/src/skf-analyze-source/references/recommend.md +4 -14
- package/src/skf-analyze-source/references/scan-project.md +3 -11
- package/src/skf-analyze-source/references/step-auto-scope.md +211 -197
- package/src/skf-analyze-source/references/step-shape-detect.md +20 -13
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +2 -2
- package/src/skf-audit-skill/SKILL.md +9 -6
- package/src/skf-audit-skill/customize.toml +2 -0
- package/src/skf-audit-skill/references/init.md +58 -32
- package/src/skf-audit-skill/references/re-index.md +15 -34
- package/src/skf-audit-skill/references/report.md +11 -58
- package/src/skf-audit-skill/references/semantic-diff.md +4 -22
- package/src/skf-audit-skill/references/severity-classify.md +53 -87
- package/src/skf-audit-skill/references/step-doc-drift.md +49 -19
- package/src/skf-audit-skill/references/structural-diff.md +43 -68
- package/src/skf-brief-skill/SKILL.md +7 -36
- package/src/skf-brief-skill/assets/scope-templates.md +1 -1
- package/src/skf-brief-skill/assets/skill-brief-schema.md +1 -1
- package/src/skf-brief-skill/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +13 -18
- package/src/skf-brief-skill/references/confirm-brief.md +2 -15
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +27 -24
- package/src/skf-brief-skill/references/headless-args.md +1 -1
- package/src/skf-brief-skill/references/health-check.md +1 -22
- package/src/skf-brief-skill/references/invocation-contract.md +37 -0
- package/src/skf-brief-skill/references/scope-definition.md +6 -15
- package/src/skf-brief-skill/references/step-auto-brief.md +22 -9
- package/src/skf-brief-skill/references/step-auto-validate.md +3 -7
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +19 -13
- package/src/skf-campaign/SKILL.md +121 -0
- package/src/skf-campaign/assets/campaign-state-schema.json +191 -0
- package/src/skf-campaign/customize.toml +75 -0
- package/src/skf-campaign/manifest.yaml +15 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +32 -0
- package/src/skf-campaign/references/health-check.md +35 -0
- package/src/skf-campaign/references/step-01-setup.md +121 -0
- package/src/skf-campaign/references/step-02-strategy.md +102 -0
- package/src/skf-campaign/references/step-03-pins.md +56 -0
- package/src/skf-campaign/references/step-04-provenance.md +63 -0
- package/src/skf-campaign/references/step-05-skill-loop.md +103 -0
- package/src/skf-campaign/references/step-06-batch.md +88 -0
- package/src/skf-campaign/references/step-07-capstone.md +63 -0
- package/src/skf-campaign/references/step-08-verify.md +75 -0
- package/src/skf-campaign/references/step-09-refine.md +83 -0
- package/src/skf-campaign/references/step-10-export.md +106 -0
- package/src/skf-campaign/references/step-11-maintenance.md +84 -0
- package/src/skf-campaign/references/step-resume.md +115 -0
- package/src/skf-campaign/scripts/.gitkeep +0 -0
- package/src/skf-campaign/scripts/campaign-deps.py +244 -0
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +119 -0
- package/src/skf-campaign/scripts/campaign-provenance.py +247 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +158 -0
- package/src/skf-campaign/scripts/campaign-report.py +249 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/scripts/campaign-validate-pins.py +174 -0
- package/src/skf-campaign/scripts/campaign-validate-state.py +207 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +34 -0
- package/src/skf-campaign/templates/campaign-report-template.md +54 -0
- package/src/skf-campaign/templates/kickoff-template.md +48 -0
- package/src/skf-create-skill/SKILL.md +15 -4
- package/src/skf-create-skill/assets/compile-assembly-rules.md +35 -34
- package/src/skf-create-skill/assets/skill-sections.md +9 -100
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +4 -13
- package/src/skf-create-skill/customize.toml +13 -0
- package/src/skf-create-skill/references/authoritative-files-protocol.md +7 -7
- package/src/skf-create-skill/references/compile.md +37 -47
- package/src/skf-create-skill/references/component-extraction.md +8 -35
- package/src/skf-create-skill/references/ecosystem-check.md +9 -25
- package/src/skf-create-skill/references/enrich.md +4 -17
- package/src/skf-create-skill/references/extract.md +20 -27
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +10 -8
- package/src/skf-create-skill/references/generate-artifacts.md +20 -25
- package/src/skf-create-skill/references/health-check.md +2 -2
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +27 -14
- package/src/skf-create-skill/references/source-resolution-protocols.md +4 -4
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +33 -15
- package/src/skf-create-skill/references/step-doc-sources.md +13 -4
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +33 -46
- package/src/skf-create-skill/references/sub/fetch-temporal.md +5 -20
- package/src/skf-create-skill/references/tier-degradation-rules.md +6 -8
- package/src/skf-create-skill/references/validate.md +69 -29
- package/src/skf-create-skill/scripts/scan-doc-rot.py +245 -0
- package/src/skf-create-stack-skill/SKILL.md +15 -9
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +1 -1
- package/src/skf-create-stack-skill/customize.toml +10 -0
- package/src/skf-create-stack-skill/references/compile-stack.md +7 -8
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +2 -13
- package/src/skf-create-stack-skill/references/detect-integrations.md +49 -54
- package/src/skf-create-stack-skill/references/detect-manifests.md +37 -30
- package/src/skf-create-stack-skill/references/generate-output.md +24 -75
- package/src/skf-create-stack-skill/references/init.md +9 -81
- package/src/skf-create-stack-skill/references/manifest-patterns.md +5 -35
- package/src/skf-create-stack-skill/references/parallel-extract.md +12 -27
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +6 -6
- package/src/skf-create-stack-skill/references/report.md +27 -62
- package/src/skf-create-stack-skill/references/validate.md +35 -31
- package/src/skf-drop-skill/SKILL.md +20 -23
- package/src/skf-drop-skill/customize.toml +10 -0
- package/src/skf-drop-skill/references/execute.md +67 -36
- package/src/skf-drop-skill/references/headless-contract.md +18 -0
- package/src/skf-drop-skill/references/health-check.md +1 -13
- package/src/skf-drop-skill/references/report.md +44 -10
- package/src/skf-drop-skill/references/select.md +93 -48
- package/src/skf-drop-skill/scripts/dir-sizes.py +116 -0
- package/src/skf-export-skill/SKILL.md +17 -14
- package/src/skf-export-skill/assets/managed-section-format.md +2 -5
- package/src/skf-export-skill/customize.toml +10 -6
- package/src/skf-export-skill/references/generate-snippet.md +21 -35
- package/src/skf-export-skill/references/load-skill.md +44 -42
- package/src/skf-export-skill/references/multi-skill-mode.md +1 -1
- package/src/skf-export-skill/references/orphan-context-detection.md +11 -3
- package/src/skf-export-skill/references/package.md +27 -33
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +24 -5
- package/src/skf-export-skill/references/result-envelope.md +28 -0
- package/src/skf-export-skill/references/summary.md +11 -5
- package/src/skf-export-skill/references/token-report.md +42 -24
- package/src/skf-export-skill/references/update-context.md +32 -33
- package/src/skf-forger/SKILL.md +32 -39
- package/src/skf-forger/references/pipeline-mode.md +34 -0
- package/src/skf-forger/scripts/parse-pipeline.py +270 -0
- package/src/skf-quick-skill/SKILL.md +10 -60
- package/src/skf-quick-skill/customize.toml +9 -0
- package/src/skf-quick-skill/references/batch-mode.md +6 -4
- package/src/skf-quick-skill/references/compile.md +15 -52
- package/src/skf-quick-skill/references/ecosystem-check.md +5 -20
- package/src/skf-quick-skill/references/finalize.md +12 -6
- package/src/skf-quick-skill/references/halt-contract.md +54 -0
- package/src/skf-quick-skill/references/health-check.md +3 -4
- package/src/skf-quick-skill/references/quick-extract.md +7 -18
- package/src/skf-quick-skill/references/registry-resolution.md +1 -23
- package/src/skf-quick-skill/references/resolve-target.md +27 -34
- package/src/skf-quick-skill/references/write-and-validate.md +10 -25
- package/src/skf-refine-architecture/SKILL.md +16 -8
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +10 -8
- package/src/skf-refine-architecture/references/gap-analysis.md +21 -50
- package/src/skf-refine-architecture/references/health-check.md +1 -1
- package/src/skf-refine-architecture/references/improvements.md +9 -25
- package/src/skf-refine-architecture/references/init.md +9 -16
- package/src/skf-refine-architecture/references/issue-detection.md +10 -25
- package/src/skf-refine-architecture/references/refinement-rules.md +4 -16
- package/src/skf-refine-architecture/references/report.md +14 -11
- package/src/skf-rename-skill/SKILL.md +11 -21
- package/src/skf-rename-skill/customize.toml +10 -0
- package/src/skf-rename-skill/references/execute.md +112 -82
- package/src/skf-rename-skill/references/exit-codes.md +12 -0
- package/src/skf-rename-skill/references/report.md +16 -8
- package/src/skf-rename-skill/references/select.md +69 -42
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -0
- package/src/skf-setup/SKILL.md +21 -13
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +11 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +10 -8
- package/src/skf-setup/references/report.md +26 -32
- package/src/skf-setup/references/tier-rules.md +2 -32
- package/src/skf-setup/references/write-config.md +9 -10
- package/src/skf-test-skill/SKILL.md +10 -8
- package/src/skf-test-skill/customize.toml +2 -0
- package/src/skf-test-skill/references/coherence-check.md +19 -31
- package/src/skf-test-skill/references/coverage-check.md +111 -67
- package/src/skf-test-skill/references/detect-mode.md +10 -41
- package/src/skf-test-skill/references/external-validators.md +11 -19
- package/src/skf-test-skill/references/init.md +39 -21
- package/src/skf-test-skill/references/migration-section-rules.md +4 -4
- package/src/skf-test-skill/references/report.md +60 -40
- package/src/skf-test-skill/references/score.md +32 -64
- package/src/skf-test-skill/references/scoring-rules.md +6 -62
- package/src/skf-test-skill/references/source-access-protocol.md +15 -5
- package/src/skf-test-skill/references/step-hard-gate.md +1 -3
- package/src/skf-test-skill/scripts/aggregate-coherence.py +276 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +359 -0
- package/src/skf-test-skill/scripts/combine-external-scores.py +190 -0
- package/src/skf-test-skill/scripts/compute-score.py +135 -4
- package/src/skf-test-skill/scripts/reconcile-coverage.py +327 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +251 -0
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +192 -0
- package/src/skf-update-skill/SKILL.md +17 -5
- package/src/skf-update-skill/customize.toml +11 -6
- package/src/skf-update-skill/references/detect-changes.md +48 -36
- package/src/skf-update-skill/references/health-check.md +5 -11
- package/src/skf-update-skill/references/init.md +40 -32
- package/src/skf-update-skill/references/manual-section-rules.md +1 -2
- package/src/skf-update-skill/references/merge-conflict-rules.md +1 -52
- package/src/skf-update-skill/references/merge.md +13 -57
- package/src/skf-update-skill/references/re-extract.md +27 -25
- package/src/skf-update-skill/references/report.md +14 -7
- package/src/skf-update-skill/references/validate.md +32 -30
- package/src/skf-update-skill/references/write.md +86 -51
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +167 -0
- package/src/skf-verify-stack/SKILL.md +11 -21
- package/src/skf-verify-stack/customize.toml +12 -3
- package/src/skf-verify-stack/references/coverage-patterns.md +1 -29
- package/src/skf-verify-stack/references/coverage.md +21 -10
- package/src/skf-verify-stack/references/exit-codes.md +14 -0
- package/src/skf-verify-stack/references/health-check.md +1 -1
- package/src/skf-verify-stack/references/init.md +20 -25
- package/src/skf-verify-stack/references/integration-verification-rules.md +10 -19
- package/src/skf-verify-stack/references/integrations.md +44 -49
- package/src/skf-verify-stack/references/report.md +28 -34
- package/src/skf-verify-stack/references/requirements.md +3 -3
- package/src/skf-verify-stack/references/synthesize.md +36 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +196 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +256 -0
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +258 -0
- package/tools/cli/lib/ui.js +3 -2
- package/docs/deepwiki.md +0 -89
- package/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -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
|
|
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
|
-
|
|
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,9 +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 `
|
|
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:
|
|
75
95
|
|
|
76
|
-
|
|
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"`).
|
|
101
|
+
|
|
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.
|
|
103
|
+
|
|
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`):
|
|
77
105
|
|
|
78
106
|
```
|
|
79
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}
|
|
@@ -81,11 +109,17 @@ SKF_DROP_SKILL_RESULT_JSON: {"status":"success","skill":"{target_skill}","drop_m
|
|
|
81
109
|
|
|
82
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.
|
|
83
111
|
|
|
84
|
-
###
|
|
112
|
+
### Post-drop hook (optional)
|
|
85
113
|
|
|
86
|
-
|
|
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:
|
|
87
115
|
|
|
88
|
-
|
|
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
|
|
89
123
|
|
|
90
|
-
|
|
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).
|
|
91
125
|
|
|
@@ -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,55 +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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
4. Sort versions in descending order (newest first) where possible
|
|
88
|
+
```bash
|
|
89
|
+
python3 {skillInventoryHelper} {skills_output_folder}
|
|
90
|
+
```
|
|
53
91
|
|
|
54
|
-
|
|
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
|
|
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
|
|
81
|
-
- **
|
|
120
|
+
- **If the user's input does not match any listed skill:**
|
|
121
|
+
- **Interactive:** Re-display the list and ask again.
|
|
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: []`.
|
|
82
123
|
|
|
83
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.
|
|
84
125
|
|
|
@@ -120,13 +161,14 @@ Store the selection as `target_skill`. Also store `target_in_manifest = true` if
|
|
|
120
161
|
|
|
121
162
|
Wait for user selection.
|
|
122
163
|
|
|
123
|
-
- 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"`).
|
|
124
|
-
|
|
125
164
|
**If [N] Specific version:**
|
|
126
165
|
|
|
127
166
|
"**Which version?** Enter the version string (e.g. `0.5.0`)."
|
|
128
167
|
|
|
129
|
-
Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`.
|
|
168
|
+
Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`.
|
|
169
|
+
|
|
170
|
+
- **If it does not match (interactive):** repeat the prompt.
|
|
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: []`.
|
|
130
172
|
|
|
131
173
|
Set `target_versions = [<selected version>]` and `is_skill_level = false`.
|
|
132
174
|
|
|
@@ -153,19 +195,21 @@ Set `target_versions = "all"` and `is_skill_level = true`.
|
|
|
153
195
|
|
|
154
196
|
**(b)** Use the `[A] All versions` option to drop every version of `{target_skill}` at once."
|
|
155
197
|
|
|
156
|
-
HALT (exit code 5, `halt_reason: "active-version-guard-refused"`). In headless mode, emit the error envelope per
|
|
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.
|
|
157
199
|
|
|
158
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)
|
|
159
201
|
|
|
160
202
|
### 8. Ask Mode
|
|
161
203
|
|
|
162
|
-
**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."
|
|
163
205
|
|
|
164
206
|
**If `target_in_manifest = true`:**
|
|
165
207
|
|
|
166
|
-
**If `
|
|
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`.
|
|
167
209
|
|
|
168
|
-
**
|
|
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.
|
|
211
|
+
|
|
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:
|
|
169
213
|
|
|
170
214
|
"**How should this be dropped?**
|
|
171
215
|
|
|
@@ -175,9 +219,7 @@ Set `target_versions = "all"` and `is_skill_level = true`.
|
|
|
175
219
|
|
|
176
220
|
Wait for user selection.
|
|
177
221
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
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"`.
|
|
181
223
|
|
|
182
224
|
### 9. Compute Affected Directories
|
|
183
225
|
|
|
@@ -200,19 +242,25 @@ If `drop_mode == "deprecate"`, record the list but present it as "retained" in t
|
|
|
200
242
|
|
|
201
243
|
#### 9b. Compute Blast-Radius Metrics (for §10 summary)
|
|
202
244
|
|
|
203
|
-
Compute three scalars to put in front of the path list at §10
|
|
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:
|
|
204
246
|
|
|
205
247
|
1. **`versions_count`** — the number of skill versions in scope:
|
|
206
248
|
- Version-level drop: `len(target_versions)` (typically `1`)
|
|
207
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)
|
|
208
250
|
|
|
209
|
-
2. **`bytes_total`** —
|
|
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.
|
|
210
258
|
|
|
211
259
|
3. **`context_files_count`** — the number of distinct context files the §3 rebuild loop will rewrite:
|
|
212
260
|
- Read `config.yaml.ides`
|
|
213
|
-
- For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use `
|
|
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)
|
|
214
262
|
- Deduplicate by `context_file`
|
|
215
|
-
- Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the
|
|
263
|
+
- Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the single `{unknownIdeDefaultContextFile}` fallback)
|
|
216
264
|
|
|
217
265
|
Store as `blast_radius = {versions_count, bytes_total, bytes_total_raw, context_files_count}` for §10's summary line.
|
|
218
266
|
|
|
@@ -241,28 +289,27 @@ Proceed? [Y/N]
|
|
|
241
289
|
|
|
242
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.
|
|
243
291
|
|
|
244
|
-
**
|
|
245
|
-
|
|
246
|
-
Wait for explicit user response.
|
|
292
|
+
**Resolve `--dry-run` first — it 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.
|
|
247
293
|
|
|
248
|
-
**If `--dry-run` was passed**: skip the Y/N prompt entirely. Display the `[DRY RUN]` line
|
|
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:
|
|
249
295
|
|
|
250
296
|
```
|
|
251
297
|
**[DRY RUN] No changes were made — preview above shows what would be dropped.**
|
|
252
298
|
|
|
253
|
-
|
|
299
|
+
Resolved selection:
|
|
254
300
|
Skill: {target_skill}
|
|
255
301
|
Version: {target_versions[0] if is_skill_level == false else "all"}
|
|
256
302
|
Mode: {Deprecate (soft) | Purge (hard)}
|
|
257
|
-
|
|
258
|
-
Re-invoke `/skf-drop-skill {target_skill}` and re-select these values, or
|
|
259
|
-
re-run with `--dry-run` to preview again.
|
|
260
303
|
```
|
|
261
304
|
|
|
262
|
-
Then emit the success envelope per
|
|
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.
|
|
263
306
|
|
|
264
|
-
-
|
|
265
|
-
|
|
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.
|
|
310
|
+
|
|
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`.
|
|
266
313
|
- **Any other input** → re-display the confirmation and ask again
|
|
267
314
|
|
|
268
315
|
### 11. Store Decisions in Context
|
|
@@ -275,12 +322,10 @@ Store the following decisions in workflow context for step 2:
|
|
|
275
322
|
- `drop_mode` — `"deprecate"` or `"purge"` (always `"purge"` when `target_in_manifest = false`)
|
|
276
323
|
- `is_skill_level` — boolean (true if all versions; always true when `target_in_manifest = false`)
|
|
277
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"`)
|
|
278
327
|
|
|
279
328
|
### 12. Load Next Step
|
|
280
329
|
|
|
281
|
-
Load, read the full file, and then execute
|
|
282
|
-
|
|
283
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
284
|
-
|
|
285
|
-
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.
|
|
286
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.
|
|
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
|
|
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
|
|
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` (
|
|
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
|
|
66
|
-
| 3 | resolution-failure | step 1 §
|
|
67
|
-
| 4 | write-failure | On-Activation §
|
|
68
|
-
| 5 | state-conflict | step 4 §
|
|
69
|
-
| 6 | user-cancelled | step 1 §6
|
|
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
|
-
`
|
|
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
|
|
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
|
-
- `{
|
|
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
|
|
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.
|