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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.nvmrc +1 -1
- package/docs/_data/pinned.yaml +1 -1
- package/package.json +2 -2
- package/src/knowledge/skill-lifecycle.md +1 -1
- package/src/module-help.csv +2 -2
- 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-detect-docs.py +232 -1
- package/src/shared/scripts/skf-detect-language.py +77 -0
- package/src/shared/scripts/skf-detect-tools.py +105 -2
- package/src/shared/scripts/skf-enumerate-stack-skills.py +111 -2
- 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-manifest-ops.py +67 -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-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 +39 -1
- 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 +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +214 -0
- package/src/shared/scripts/skf-verify-provenance-completeness.py +439 -0
- package/src/skf-analyze-source/SKILL.md +17 -32
- 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 +42 -180
- 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/customize.toml +14 -0
- package/src/skf-brief-skill/references/analyze-target.md +6 -14
- package/src/skf-brief-skill/references/confirm-brief.md +2 -14
- package/src/skf-brief-skill/references/draft-checkpoint.md +8 -8
- package/src/skf-brief-skill/references/gather-intent.md +19 -22
- 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 +4 -14
- package/src/skf-brief-skill/references/step-auto-brief.md +1 -5
- package/src/skf-brief-skill/references/step-auto-validate.md +1 -5
- package/src/skf-brief-skill/references/version-resolution.md +1 -1
- package/src/skf-brief-skill/references/write-brief.md +17 -10
- package/src/skf-campaign/SKILL.md +15 -84
- package/src/skf-campaign/customize.toml +2 -0
- package/src/skf-campaign/manifest.yaml +4 -0
- package/src/skf-campaign/references/campaign-contracts.md +67 -0
- package/src/skf-campaign/references/campaign-directive-spec.md +14 -103
- package/src/skf-campaign/references/step-01-setup.md +4 -5
- package/src/skf-campaign/references/step-02-strategy.md +8 -3
- package/src/skf-campaign/references/step-03-pins.md +1 -1
- package/src/skf-campaign/references/step-04-provenance.md +1 -1
- package/src/skf-campaign/references/step-05-skill-loop.md +1 -1
- package/src/skf-campaign/references/step-06-batch.md +7 -6
- package/src/skf-campaign/references/step-11-maintenance.md +2 -2
- package/src/skf-campaign/references/step-resume.md +9 -8
- package/src/skf-campaign/scripts/campaign-deps.py +10 -1
- package/src/skf-campaign/scripts/campaign-render-batch.py +199 -0
- package/src/skf-campaign/scripts/campaign-status.py +162 -0
- package/src/skf-campaign/templates/campaign-brief-template.yaml +1 -1
- package/src/skf-create-skill/SKILL.md +14 -3
- package/src/skf-create-skill/assets/compile-assembly-rules.md +17 -38
- 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 +34 -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 +14 -29
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +1 -1
- package/src/skf-create-skill/references/extraction-patterns.md +7 -7
- package/src/skf-create-skill/references/generate-artifacts.md +9 -23
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +16 -18
- package/src/skf-create-skill/references/report.md +12 -15
- package/src/skf-create-skill/references/source-resolution-protocols.md +3 -3
- package/src/skf-create-skill/references/step-auto-shard.md +52 -25
- package/src/skf-create-skill/references/step-doc-rot.md +30 -12
- package/src/skf-create-skill/references/step-doc-sources.md +2 -2
- package/src/skf-create-skill/references/sub/ccc-discover.md +2 -13
- package/src/skf-create-skill/references/sub/fetch-docs.md +8 -50
- 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 +59 -28
- 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 +51 -31
- 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 +42 -10
- package/src/skf-drop-skill/references/select.md +88 -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 +27 -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 +14 -7
- package/src/skf-refine-architecture/customize.toml +9 -0
- package/src/skf-refine-architecture/references/compile.md +9 -7
- 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 +13 -10
- 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 +107 -78
- 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 +20 -12
- package/src/skf-setup/customize.toml +10 -0
- package/src/skf-setup/references/auto-index.md +9 -10
- package/src/skf-setup/references/ccc-index.md +14 -15
- package/src/skf-setup/references/detect-and-tier.md +6 -8
- package/src/skf-setup/references/report.md +22 -31
- 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 +109 -75
- 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 +38 -20
- 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 +6 -6
- 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 +131 -1
- 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 +14 -0
- package/src/skf-update-skill/references/detect-changes.md +19 -37
- package/src/skf-update-skill/references/health-check.md +3 -9
- package/src/skf-update-skill/references/init.md +39 -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 +12 -24
- 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/src/skf-rename-skill/references/rebuild-context.md +0 -110
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'execute.md'
|
|
3
3
|
versionPathsKnowledge: 'knowledge/version-paths.md'
|
|
4
|
+
# Read-side inventory helpers (reads, not atomicity-critical). §2 uses
|
|
5
|
+
# `{manifestOpsHelper} read` (manifest parse + v1→v2 migration + corrupt-JSON
|
|
6
|
+
# detection); §3 uses `{skillInventoryHelper}` (on-disk scan + exports∪on-disk
|
|
7
|
+
# diff for orphan detection) and `{manifestOpsHelper} affected-versions <skill>`
|
|
8
|
+
# (numeric semver-descending order, so 0.10.0 precedes 0.9.0 — LLM-unreliable).
|
|
9
|
+
# Probe each in order (installed SKF path first, src/ fallback); first hit wins.
|
|
10
|
+
# If neither candidate resolves, the section computes the result in-prompt.
|
|
11
|
+
manifestOpsProbeOrder:
|
|
12
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-manifest-ops.py'
|
|
13
|
+
- '{project-root}/src/shared/scripts/skf-manifest-ops.py'
|
|
14
|
+
skillInventoryProbeOrder:
|
|
15
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
|
|
16
|
+
- '{project-root}/src/shared/scripts/skf-skill-inventory.py'
|
|
17
|
+
# Standalone single-line result-envelope contract emitted at every headless
|
|
18
|
+
# HALT in this step. Loaded in §1 so no error path depends on SKILL.md
|
|
19
|
+
# remaining in context under compaction.
|
|
20
|
+
headlessContract: 'headless-contract.md'
|
|
21
|
+
# Deterministic recursive byte sizing + human formatting for the §9b
|
|
22
|
+
# blast-radius line. Bundled with this skill (no probe order needed);
|
|
23
|
+
# execute.md §4 reuses the same helper for the canonical `disk_freed`.
|
|
24
|
+
dirSizesHelper: 'scripts/dir-sizes.py'
|
|
4
25
|
---
|
|
5
26
|
|
|
6
27
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -17,6 +38,7 @@ Identify exactly what the user wants to drop — which skill, which version(s),
|
|
|
17
38
|
- Do not proceed without explicit user confirmation at the final gate
|
|
18
39
|
- Do not drop an active version when other non-deprecated versions exist
|
|
19
40
|
- Present selections clearly so the user can verify scope, mode, and blast radius
|
|
41
|
+
- **Interactive cancel (every gate below):** at any prompt, `cancel` / `exit` / `[X]` / `q` / `:q` → display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`). Stated once here; the §10 commit gate adds its own tip + headless envelope on top of this.
|
|
20
42
|
|
|
21
43
|
## MANDATORY SEQUENCE
|
|
22
44
|
|
|
@@ -30,57 +52,74 @@ Read `{versionPathsKnowledge}` completely and extract:
|
|
|
30
52
|
|
|
31
53
|
You will use these templates and rules to build directory paths and enforce safety guards in the following sections.
|
|
32
54
|
|
|
55
|
+
If `{headless_mode}` is true, also read `{headlessContract}` now — it defines the single-line result envelope every HALT below emits, so the shape is in context even if SKILL.md was compacted out.
|
|
56
|
+
|
|
33
57
|
### 2. Read Export Manifest
|
|
34
58
|
|
|
35
|
-
|
|
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 enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
|
|
81
120
|
- **If the user's input does not match any listed skill:**
|
|
82
121
|
- **Interactive:** Re-display the list and ask again.
|
|
83
|
-
- **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: skill argument `{supplied value}` does not match any listed skill." Emit the error envelope per
|
|
122
|
+
- **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: skill argument `{supplied value}` does not match any listed skill." Emit the error envelope per `{headlessContract}` with `skill: null`, `drop_mode: null`, `versions_affected: []`.
|
|
84
123
|
|
|
85
124
|
Store the selection as `target_skill`. Also store `target_in_manifest = true` if the selected skill has an entry in the manifest, `false` otherwise — subsequent sections use this flag to restrict the available drop options.
|
|
86
125
|
|
|
@@ -122,8 +161,6 @@ Store the selection as `target_skill`. Also store `target_in_manifest = true` if
|
|
|
122
161
|
|
|
123
162
|
Wait for user selection.
|
|
124
163
|
|
|
125
|
-
- If the user enters `cancel`, `exit`, `[X]`, `q`, or `:q`: Display "Cancelled — no changes were made." and HALT (exit code 6, `halt_reason: "user-cancelled"`).
|
|
126
|
-
|
|
127
164
|
**If [N] Specific version:**
|
|
128
165
|
|
|
129
166
|
"**Which version?** Enter the version string (e.g. `0.5.0`)."
|
|
@@ -131,7 +168,7 @@ Wait for user selection.
|
|
|
131
168
|
Wait for user input. Validate that the version exists in the manifest's `versions` map for `target_skill`.
|
|
132
169
|
|
|
133
170
|
- **If it does not match (interactive):** repeat the prompt.
|
|
134
|
-
- **If it does not match (headless — `{headless_mode}` is true):** the supplied `version` argument is unparseable or absent from the `versions` map — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions." Emit the error envelope per
|
|
171
|
+
- **If it does not match (headless — `{headless_mode}` is true):** the supplied `version` argument is unparseable or absent from the `versions` map — there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`): "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions." Emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: []`.
|
|
135
172
|
|
|
136
173
|
Set `target_versions = [<selected version>]` and `is_skill_level = false`.
|
|
137
174
|
|
|
@@ -158,19 +195,21 @@ Set `target_versions = "all"` and `is_skill_level = true`.
|
|
|
158
195
|
|
|
159
196
|
**(b)** Use the `[A] All versions` option to drop every version of `{target_skill}` at once."
|
|
160
197
|
|
|
161
|
-
HALT (exit code 5, `halt_reason: "active-version-guard-refused"`). In headless mode, emit the error envelope per
|
|
198
|
+
HALT (exit code 5, `halt_reason: "active-version-guard-refused"`). In headless mode, emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`, `versions_affected: ["{version}"]`. Do not proceed.
|
|
162
199
|
|
|
163
200
|
c. If the count is `0` → the active version is the ONLY version; allow the drop to continue (it is functionally equivalent to a skill-level drop on a single-version skill)
|
|
164
201
|
|
|
165
202
|
### 8. Ask Mode
|
|
166
203
|
|
|
167
|
-
**If `target_in_manifest = false`:** Skip this prompt — soft-deprecate is meaningless without a manifest entry to mark. Force `drop_mode = "purge"` and inform the user: "**Mode forced to purge** — `{target_skill}` has no manifest entry, so there is nothing to deprecate. The skill's on-disk directories will be deleted."
|
|
204
|
+
**If `target_in_manifest = false`:** Skip this prompt — soft-deprecate is meaningless without a manifest entry to mark. Force `drop_mode = "purge"` (record `mode_source = "draft-skill-forced-purge"`) and inform the user: "**Mode forced to purge** — `{target_skill}` has no manifest entry, so there is nothing to deprecate. The skill's on-disk directories will be deleted."
|
|
168
205
|
|
|
169
206
|
**If `target_in_manifest = true`:**
|
|
170
207
|
|
|
171
|
-
**If `
|
|
208
|
+
**If a `mode` argument was supplied at invocation:** an explicit `mode` arg is the per-run override and takes precedence over `{defaultMode}`. If it is `"deprecate"` or `"purge"`, set `drop_mode` from it and record the decision source `mode_source = "--mode argument"`. If a `mode` arg was supplied but is not one of `deprecate` / `purge`, HALT (exit code 2, `halt_reason: "input-invalid"`): "invalid `--mode` value `{supplied}` — expected `deprecate` or `purge`." In headless mode, emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`.
|
|
209
|
+
|
|
210
|
+
**Else if `{defaultMode}` is non-empty (`"deprecate"` or `"purge"`)**: skip the prompt, set `drop_mode = "{defaultMode}"`, and record the decision source `mode_source = "customize.toml.workflow.default_mode"` for the headless decision trail.
|
|
172
211
|
|
|
173
|
-
**Otherwise:**
|
|
212
|
+
**Otherwise (interactive):** If `{headless_mode}` is true at this point (no `mode` arg and no `{defaultMode}`), there is no input to prompt for — HALT (exit code 2, `halt_reason: "input-missing"`): "headless mode requires `--mode deprecate|purge` or `default_mode` in customize.toml to set the drop mode." Emit the error envelope per `{headlessContract}` with `skill: "{target_skill}"`, `drop_mode: null`. Otherwise, prompt the user:
|
|
174
213
|
|
|
175
214
|
"**How should this be dropped?**
|
|
176
215
|
|
|
@@ -180,9 +219,7 @@ Set `target_versions = "all"` and `is_skill_level = true`.
|
|
|
180
219
|
|
|
181
220
|
Wait for user selection.
|
|
182
221
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
Set `drop_mode` to `"deprecate"` (on D) or `"purge"` (on P).
|
|
222
|
+
Set `drop_mode` to `"deprecate"` (on D) or `"purge"` (on P), and record `mode_source = "interactive-prompt"`.
|
|
186
223
|
|
|
187
224
|
### 9. Compute Affected Directories
|
|
188
225
|
|
|
@@ -205,19 +242,25 @@ If `drop_mode == "deprecate"`, record the list but present it as "retained" in t
|
|
|
205
242
|
|
|
206
243
|
#### 9b. Compute Blast-Radius Metrics (for §10 summary)
|
|
207
244
|
|
|
208
|
-
Compute three scalars to put in front of the path list at §10
|
|
245
|
+
Compute three scalars to put in front of the path list at §10, so the user sees the scale of an irreversible drop before scanning individual paths:
|
|
209
246
|
|
|
210
247
|
1. **`versions_count`** — the number of skill versions in scope:
|
|
211
248
|
- Version-level drop: `len(target_versions)` (typically `1`)
|
|
212
249
|
- Skill-level drop: count of non-deprecated versions in `exports.{target_skill}.versions` (the deprecated ones are already absent from the active managed sections)
|
|
213
250
|
|
|
214
|
-
2. **`bytes_total`** —
|
|
251
|
+
2. **`bytes_total`** — the on-disk size of `affected_directories`. Delegate the recursive sum and the human label to the sizing helper rather than adding file sizes in-prompt:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
uv run {dirSizesHelper} sizes {each path in affected_directories, space-separated}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Read `total_human` (e.g. `"4.2 MB"`) as `bytes_total` and `total_bytes` as `bytes_total_raw`; non-existent paths report `exists: false` and drop out of the total. If the helper is unavailable, fall back to `du -sb` per path — the display is best-effort. execute.md §4 re-runs the same helper on the paths it actually deletes for the canonical `disk_freed`, so the two share one method and differ only if files change between this gate and execution.
|
|
215
258
|
|
|
216
259
|
3. **`context_files_count`** — the number of distinct context files the §3 rebuild loop will rewrite:
|
|
217
260
|
- Read `config.yaml.ides`
|
|
218
|
-
- For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use `
|
|
261
|
+
- For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use the `{unknownIdeDefaultContextFile}` fallback for unknown IDEs)
|
|
219
262
|
- Deduplicate by `context_file`
|
|
220
|
-
- Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the
|
|
263
|
+
- Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the single `{unknownIdeDefaultContextFile}` fallback)
|
|
221
264
|
|
|
222
265
|
Store as `blast_radius = {versions_count, bytes_total, bytes_total_raw, context_files_count}` for §10's summary line.
|
|
223
266
|
|
|
@@ -246,28 +289,27 @@ Proceed? [Y/N]
|
|
|
246
289
|
|
|
247
290
|
The `Scope:` line is the §9b-computed `blast_radius` rendered as one line. In `deprecate` mode the `~{bytes_total} on disk` reads as "size that will remain on disk (soft drop — files retained)"; the user is still served by knowing it. In `purge` mode it reads as "approximate disk that will be freed". The wording stays the same — the surrounding `Mode:` field disambiguates intent.
|
|
248
291
|
|
|
249
|
-
**
|
|
250
|
-
|
|
251
|
-
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.
|
|
252
293
|
|
|
253
|
-
**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:
|
|
254
295
|
|
|
255
296
|
```
|
|
256
297
|
**[DRY RUN] No changes were made — preview above shows what would be dropped.**
|
|
257
298
|
|
|
258
|
-
|
|
299
|
+
Resolved selection:
|
|
259
300
|
Skill: {target_skill}
|
|
260
301
|
Version: {target_versions[0] if is_skill_level == false else "all"}
|
|
261
302
|
Mode: {Deprecate (soft) | Purge (hard)}
|
|
262
|
-
|
|
263
|
-
Re-invoke `/skf-drop-skill {target_skill}` and re-select these values, or
|
|
264
|
-
re-run with `--dry-run` to preview again.
|
|
265
303
|
```
|
|
266
304
|
|
|
267
|
-
Then emit the success envelope per
|
|
305
|
+
Then emit the success envelope per `{headlessContract}` with `status: "dry-run"`, the resolved `skill`, `drop_mode`, and `versions_affected`, then HALT (exit code 0). The manifest, filesystem, and context files are untouched.
|
|
306
|
+
|
|
307
|
+
**Otherwise (not a dry-run) — GATE [default: Y]:** If `{headless_mode}`: auto-proceed with [Y], record `confirm_source = "headless-auto"`, log: "headless: auto-confirmed drop of {target_skill}"
|
|
308
|
+
|
|
309
|
+
Wait for explicit user response.
|
|
268
310
|
|
|
269
|
-
- **If `Y`** → proceed to section 11
|
|
270
|
-
- **If `N`** (or `cancel` / `exit` / `[X]` / `:q`) → "**Cancelled.** No changes were made. (Tip: invoke with `--dry-run` next time to preview the operation without reaching the commit prompt.)" HALT (exit code 6, `halt_reason: "user-cancelled"`). In headless mode, emit the error envelope per
|
|
311
|
+
- **If `Y`** → record `confirm_source = "user-explicit"` and proceed to section 11
|
|
312
|
+
- **If `N`** (or `cancel` / `exit` / `[X]` / `:q`) → "**Cancelled.** No changes were made. (Tip: invoke with `--dry-run` next time to preview the operation without reaching the commit prompt.)" HALT (exit code 6, `halt_reason: "user-cancelled"`). In headless mode, emit the error envelope per `{headlessContract}` with the resolved `skill`, `drop_mode`, and `versions_affected`.
|
|
271
313
|
- **Any other input** → re-display the confirmation and ask again
|
|
272
314
|
|
|
273
315
|
### 11. Store Decisions in Context
|
|
@@ -280,12 +322,10 @@ Store the following decisions in workflow context for step 2:
|
|
|
280
322
|
- `drop_mode` — `"deprecate"` or `"purge"` (always `"purge"` when `target_in_manifest = false`)
|
|
281
323
|
- `is_skill_level` — boolean (true if all versions; always true when `target_in_manifest = false`)
|
|
282
324
|
- `affected_directories` — list of absolute directory paths that step 2 will delete in purge mode (or retain in deprecate mode)
|
|
325
|
+
- `mode_source` — where `drop_mode` was decided, set inline at §8 (one of the four sources named there)
|
|
326
|
+
- `confirm_source` — how the §10 gate was cleared, set inline at §10 (`"headless-auto"` or `"user-explicit"`)
|
|
283
327
|
|
|
284
328
|
### 12. Load Next Step
|
|
285
329
|
|
|
286
|
-
Load, read the full file, and then execute
|
|
287
|
-
|
|
288
|
-
## CRITICAL STEP COMPLETION NOTE
|
|
289
|
-
|
|
290
|
-
ONLY WHEN the user has confirmed with `Y` at the confirmation gate AND all selection decisions have been stored in context, will you then load and read fully `{nextStepFile}` to execute the drop.
|
|
330
|
+
`{nextStepFile}` performs the destructive mutation, so reach it only after the §10 gate returned `Y` and §11 stored the decisions — chaining any earlier would drop without the user's explicit consent (the sequence above enforces this ordering). Load, read the full file, and then execute it.
|
|
291
331
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.10"
|
|
4
|
+
# ///
|
|
5
|
+
"""Deterministic directory-size measurement for skf-drop-skill.
|
|
6
|
+
|
|
7
|
+
The drop workflow twice needs an exact recursive byte total plus a stable
|
|
8
|
+
human label ("4.2 MB" / "812 KB"): select.md §9b renders the blast-radius
|
|
9
|
+
line ahead of the confirmation gate, and execute.md §4 reports the canonical
|
|
10
|
+
`disk_freed`. Summing file sizes and rounding a unit in the prompt has one
|
|
11
|
+
correct answer per input, so it belongs here — identical input yields
|
|
12
|
+
identical output, and the gate preview and the post-purge report agree on the
|
|
13
|
+
method (they can still differ only because they read at different times).
|
|
14
|
+
|
|
15
|
+
Two operations:
|
|
16
|
+
|
|
17
|
+
sizes <path>... recursive byte size of each path (best-effort: a
|
|
18
|
+
non-existent path reports exists=false, bytes=0 and is
|
|
19
|
+
excluded from the total). Symlinks are measured by the
|
|
20
|
+
size of the link itself, never followed.
|
|
21
|
+
humanize <bytes>... sum a set of already-measured byte counts and format
|
|
22
|
+
one human label. execute.md §4 feeds it the sizes of
|
|
23
|
+
the paths that were actually deleted.
|
|
24
|
+
|
|
25
|
+
Output is a single JSON object on stdout. Exit codes: 0 ok; 2 usage error
|
|
26
|
+
(missing/unknown op, or a non-integer byte count for `humanize`).
|
|
27
|
+
"""
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import json
|
|
31
|
+
import os
|
|
32
|
+
import sys
|
|
33
|
+
|
|
34
|
+
# 1024-based, matching the `du -h` feel the step files reference. Labelled
|
|
35
|
+
# KB/MB for readability; the raw integer total travels alongside for any
|
|
36
|
+
# consumer that needs exact arithmetic.
|
|
37
|
+
_UNITS = ["B", "KB", "MB", "GB", "TB", "PB"]
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def humanize_bytes(n: int) -> str:
|
|
41
|
+
size = float(n)
|
|
42
|
+
for unit in _UNITS:
|
|
43
|
+
if size < 1024 or unit == _UNITS[-1]:
|
|
44
|
+
if unit == "B":
|
|
45
|
+
return f"{int(size)} {unit}"
|
|
46
|
+
return f"{size:.1f} {unit}"
|
|
47
|
+
size /= 1024
|
|
48
|
+
return f"{int(n)} B" # unreachable; keeps type checkers happy
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def dir_bytes(path: str) -> int:
|
|
52
|
+
"""Recursive byte size of a file, directory, or symlink (link not followed)."""
|
|
53
|
+
if os.path.islink(path):
|
|
54
|
+
return os.lstat(path).st_size
|
|
55
|
+
if os.path.isfile(path):
|
|
56
|
+
return os.path.getsize(path)
|
|
57
|
+
total = 0
|
|
58
|
+
for root, _dirs, files in os.walk(path):
|
|
59
|
+
for name in files:
|
|
60
|
+
fp = os.path.join(root, name)
|
|
61
|
+
try:
|
|
62
|
+
total += os.lstat(fp).st_size
|
|
63
|
+
except OSError:
|
|
64
|
+
pass
|
|
65
|
+
return total
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _op_sizes(paths: list[str]) -> dict:
|
|
69
|
+
entries = []
|
|
70
|
+
total = 0
|
|
71
|
+
for p in paths:
|
|
72
|
+
exists = os.path.exists(p) or os.path.islink(p)
|
|
73
|
+
b = dir_bytes(p) if exists else 0
|
|
74
|
+
if exists:
|
|
75
|
+
total += b
|
|
76
|
+
entries.append({"path": p, "exists": exists, "bytes": b, "human": humanize_bytes(b)})
|
|
77
|
+
return {
|
|
78
|
+
"status": "ok",
|
|
79
|
+
"op": "sizes",
|
|
80
|
+
"paths": entries,
|
|
81
|
+
"total_bytes": total,
|
|
82
|
+
"total_human": humanize_bytes(total),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _op_humanize(raw: list[str]) -> dict:
|
|
87
|
+
total = 0
|
|
88
|
+
for value in raw:
|
|
89
|
+
try:
|
|
90
|
+
total += int(value)
|
|
91
|
+
except (TypeError, ValueError):
|
|
92
|
+
print(
|
|
93
|
+
json.dumps({"status": "error", "error": f"not an integer byte count: {value!r}"}),
|
|
94
|
+
file=sys.stderr,
|
|
95
|
+
)
|
|
96
|
+
sys.exit(2)
|
|
97
|
+
return {"status": "ok", "op": "humanize", "total_bytes": total, "total_human": humanize_bytes(total)}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def main(argv: list[str]) -> int:
|
|
101
|
+
if not argv:
|
|
102
|
+
print(json.dumps({"status": "error", "error": "usage: dir-sizes.py {sizes|humanize} <args>"}), file=sys.stderr)
|
|
103
|
+
return 2
|
|
104
|
+
op, args = argv[0], argv[1:]
|
|
105
|
+
if op == "sizes":
|
|
106
|
+
print(json.dumps(_op_sizes(args)))
|
|
107
|
+
return 0
|
|
108
|
+
if op == "humanize":
|
|
109
|
+
print(json.dumps(_op_humanize(args)))
|
|
110
|
+
return 0
|
|
111
|
+
print(json.dumps({"status": "error", "error": f"unknown op: {op!r}"}), file=sys.stderr)
|
|
112
|
+
return 2
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
sys.exit(main(sys.argv[1:]))
|
|
@@ -7,20 +7,21 @@ description: Package for distribution and inject context into CLAUDE.md/AGENTS.m
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
Packages a completed skill as an agentskills.io-compliant package, generates context snippets, and updates the managed section in CLAUDE.md/.cursorrules/AGENTS.md for platform-aware context injection.
|
|
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.
|
|
@@ -122,7 +122,7 @@ Target file contains `<!-- SKF:BEGIN` but no matching `<!-- SKF:END -->` marker.
|
|
|
122
122
|
When regenerating (Case 3) or creating/appending (Cases 1-2), rebuild the skill index from the **exported skill set** only:
|
|
123
123
|
|
|
124
124
|
1. Read `{skills_output_folder}/.export-manifest.json` (v2 schema — see `knowledge/version-paths.md`) to determine which skills have been explicitly exported and their `active_version` (if no manifest exists, only the current export target qualifies)
|
|
125
|
-
2. For each skill in the exported set, resolve the snippet at `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/context-snippet.md` (i.e., `{skill_package}/context-snippet.md`). Do NOT use glob patterns across version directories — always resolve via manifest `active_version` or `active` symlink
|
|
125
|
+
2. For each skill in the exported set, resolve the snippet at `{skills_output_folder}/{skill-name}/{active_version}/{skill-name}/context-snippet.md` (i.e., `{skill_package}/context-snippet.md`). Do NOT use glob patterns across version directories — always resolve via manifest `active_version` or `active` symlink, falling back to the flat `{skills_output_folder}/{skill-name}/context-snippet.md` path for a skill not yet migrated to the versioned layout
|
|
126
126
|
3. Count total skills and stack skills (from filtered set only)
|
|
127
127
|
4. Assemble filtered snippets into managed section
|
|
128
128
|
5. Sort alphabetically by skill name
|
|
@@ -132,7 +132,4 @@ When regenerating (Case 3) or creating/appending (Cases 1-2), rebuild the skill
|
|
|
132
132
|
|
|
133
133
|
## Safety Rules
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
- ALWAYS preserve existing file content above and below markers
|
|
137
|
-
- ALWAYS verify file was written correctly after write
|
|
138
|
-
- If write fails, report error — do not attempt partial writes
|
|
135
|
+
Only the bytes between `<!-- SKF:BEGIN -->` and `<!-- SKF:END -->` are yours to rewrite; everything above and below is the user's own file (their CLAUDE.md/AGENTS.md/.cursorrules) and must survive byte-for-byte — rewriting outside the markers destroys their content. On write failure, report and stop — never leave a partial write. Route writes through the atomic-write / rebuild-managed-sections helpers: they verify byte-identity of the content outside the markers after each write, so no separate hand-check is needed.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-export-skill.
|
|
4
|
+
# Team overrides: _bmad/custom/skf-export-skill.toml (under {project-root})
|
|
5
|
+
# Personal overrides: _bmad/custom/skf-export-skill.user.toml (under {project-root})
|
|
4
6
|
|
|
5
7
|
[workflow]
|
|
6
8
|
|
|
@@ -34,6 +36,14 @@ persistent_facts = [
|
|
|
34
36
|
"file:{project-root}/**/project-context.md",
|
|
35
37
|
]
|
|
36
38
|
|
|
39
|
+
# Optional post-export hook. When non-empty, invoked with
|
|
40
|
+
# `--result-path=<path>` after step 6 finalizes the per-run result JSON
|
|
41
|
+
# (before the terminal health-check step). Useful for downstream
|
|
42
|
+
# distribution, notification, or pipeline chaining. Failures are logged to
|
|
43
|
+
# workflow_warnings[] but never fail the workflow. Empty = no-op (default).
|
|
44
|
+
|
|
45
|
+
on_complete = ""
|
|
46
|
+
|
|
37
47
|
# --- Optional asset overrides ---
|
|
38
48
|
#
|
|
39
49
|
# Lift the canonical asset paths so orgs can substitute house-style copies
|
|
@@ -41,9 +51,3 @@ persistent_facts = [
|
|
|
41
51
|
|
|
42
52
|
managed_section_format_path = ""
|
|
43
53
|
snippet_format_path = ""
|
|
44
|
-
|
|
45
|
-
# Override the manifest location. Empty = use
|
|
46
|
-
# {skills_output_folder}/.export-manifest.json. Useful for orgs that want
|
|
47
|
-
# a cross-project manifest at a stable path (e.g. ~/.skf/export-manifest.json).
|
|
48
|
-
|
|
49
|
-
export_manifest_path = ""
|