bmad-module-skill-forge 2.2.0 → 4.0.0
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/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +143 -18
- package/docs/verifying-a-skill.md +87 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +3 -3
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +31 -59
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +233 -64
- package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +772 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +21 -3
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +197 -55
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +5247 -96
- package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +664 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +469 -205
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +700 -0
- package/src/shared/scripts/skf-hash-content.py +180 -20
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +161 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +143 -20
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +176 -28
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +155 -14
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +787 -121
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +120 -67
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +23 -2
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +3945 -152
- package/src/shared/scripts/skf-write-skill-brief.py +406 -25
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +28 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/health-check.md +2 -0
- package/src/skf-audit-skill/references/init.md +149 -210
- package/src/skf-audit-skill/references/re-index.md +103 -110
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +34 -25
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +80 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
- package/src/skf-brief-skill/SKILL.md +37 -26
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +18 -25
- package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +144 -127
- package/src/skf-brief-skill/references/confirm-brief.md +11 -9
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +7 -10
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
- package/src/skf-create-skill/assets/skill-sections.md +21 -11
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +119 -70
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
- package/src/skf-create-skill/references/extract.md +204 -162
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
- package/src/skf-create-skill/references/extraction-patterns.md +588 -201
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +37 -57
- package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +83 -146
- package/src/skf-drop-skill/references/health-check.md +2 -0
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +122 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +209 -264
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +164 -91
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +164 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +42 -155
- package/src/skf-test-skill/references/source-access-protocol.md +18 -94
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +380 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
- package/src/skf-test-skill/scripts/score-signatures.py +479 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +253 -223
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +254 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +182 -49
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +102 -35
- package/src/skf-update-skill/references/write.md +357 -155
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
nextStepFile: 'execute.md'
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#
|
|
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'
|
|
3
|
+
# {dropRosterHelper}: the §2 roster.
|
|
4
|
+
dropRosterHelper: 'scripts/drop-roster.py'
|
|
5
|
+
# {skillInventoryHelper}: the §8b purge check.
|
|
14
6
|
skillInventoryProbeOrder:
|
|
15
7
|
- '{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py'
|
|
16
8
|
- '{project-root}/src/shared/scripts/skf-skill-inventory.py'
|
|
17
|
-
#
|
|
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`.
|
|
9
|
+
# {dirSizesHelper}: the §9b size of the folders in scope.
|
|
24
10
|
dirSizesHelper: 'scripts/dir-sizes.py'
|
|
11
|
+
# {atomicWriteHelper}: the §7 recovery that points `active` at the version to keep.
|
|
12
|
+
atomicWriteProbeOrder:
|
|
13
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-atomic-write.py'
|
|
14
|
+
- '{project-root}/src/shared/scripts/skf-atomic-write.py'
|
|
25
15
|
---
|
|
26
16
|
|
|
27
17
|
<!-- Config: communicate in {communication_language}. -->
|
|
@@ -34,66 +24,47 @@ Identify exactly what the user wants to drop — which skill, which version(s),
|
|
|
34
24
|
|
|
35
25
|
## Rules
|
|
36
26
|
|
|
37
|
-
- Focus only on selection, validation, and confirmation
|
|
27
|
+
- Focus only on selection, validation, and confirmation: do not change the manifest, a context file or a skill folder (this step writes only its envelope payloads, in `{run_dir}`)
|
|
38
28
|
- Do not proceed without explicit user confirmation at the final gate
|
|
39
29
|
- Do not drop an active version when other non-deprecated versions exist
|
|
40
30
|
- 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
|
|
31
|
+
- **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 on top of this.
|
|
42
32
|
|
|
43
33
|
## MANDATORY SEQUENCE
|
|
44
34
|
|
|
45
|
-
### 1.
|
|
35
|
+
### 1. Halt Envelope
|
|
46
36
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- Path templates: `{skill_package}`, `{skill_group}`, `{forge_version}`, `{forge_group}`
|
|
50
|
-
- Export manifest v2 schema (`schema_version`, `exports`, `active_version`, `versions` map, `status` field values)
|
|
51
|
-
- Skill management operations (Drop section — soft vs hard, active version guard, skill-level drop)
|
|
52
|
-
|
|
53
|
-
You will use these templates and rules to build directory paths and enforce safety guards in the following sections.
|
|
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
|
-
|
|
57
|
-
### 2. Read Export Manifest
|
|
58
|
-
|
|
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:
|
|
37
|
+
Every HALT in this step names its exit code, `halt_reason` and phase. In headless mode it also prints its envelope through the shared emitter before it stops (`{emitEnvelopeHelper}` and `{run_dir}` come from SKILL.md On-Activation §4): stage `{run_dir}/halt.json` as `{"phase": "<phase>", "reason": "<the halt message>", "halt_reason": "<halt_reason>", "exit_code": <code>}` plus the envelope fields the site names (and `"path"` when the halt names one), then run
|
|
60
38
|
|
|
61
39
|
```bash
|
|
62
|
-
|
|
40
|
+
uv run {emitEnvelopeHelper} emit-halt --workflow skf-drop-skill --run-dir "{run_dir}" --target stderr < "{run_dir}/halt.json"
|
|
63
41
|
```
|
|
64
42
|
|
|
65
|
-
|
|
66
|
-
|
|
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`).
|
|
43
|
+
and display the line it prints verbatim. Write the payload as valid JSON: in the halt message and `path`, replace each backslash with / and each double quote with a backtick. The emitter gives every field the site leaves out its default (`null`, `[]` or `false`) and checks `exit_code` against `halt_reason`. If it exits non-zero or prints no line, display the halt message alone. An interactive HALT displays its message, emits nothing and then deletes the run folder (`rm -rf "{run_dir}"`), which nothing reads after it.
|
|
69
44
|
|
|
70
|
-
|
|
45
|
+
### 2. Read the Roster
|
|
71
46
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
### 3. List Available Skills
|
|
75
|
-
|
|
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:
|
|
47
|
+
Read every skill this drop can offer in one call:
|
|
79
48
|
|
|
80
49
|
```bash
|
|
81
|
-
|
|
50
|
+
uv run {dropRosterHelper} skills "{skills_output_folder}" --forge-data-folder "{forge_data_folder}" [--skill "{skill_name}"]
|
|
82
51
|
```
|
|
83
52
|
|
|
84
|
-
`
|
|
53
|
+
Pass `--skill` when a `skill_name` argument was supplied: the roster then holds that skill only. Bind `{roster}` ← the result.
|
|
85
54
|
|
|
86
|
-
|
|
55
|
+
- **`code` is `manifest-corrupt`** (the file exists but does not parse): 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"`, phase `select:manifest-read`, path `{skills_output_folder}/.export-manifest.json`) with the §1 halt envelope.
|
|
56
|
+
- **Any other non-zero exit, or no JSON on stdout:** HALT (exit code 4, `halt_reason: "write-failed"`, phase `select:roster`) with the §1 halt envelope: "SKF cannot list the skills to drop: {the result's `error`, or the first stderr line}. Nothing was changed. Re-install SKF."
|
|
87
57
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
58
|
+
### 3. List Available Skills
|
|
59
|
+
|
|
60
|
+
`{roster}.skills` holds every skill in the export manifest and every skill SKF generated that the manifest does not list (`purge_only` true: a draft, which only a purge can drop), each with its `versions` newest first. Bind `{not_offered}` ← the names in `{roster}.not_offered`: folders holding a skill SKF did not generate, which the roster never offers. When `{roster}.inventory` is false (the inventory helper could not run), the roster holds the manifest skills alone, because SKF cannot check that it generated a folder the manifest does not list, and `{not_offered}` is empty.
|
|
91
61
|
|
|
92
|
-
|
|
62
|
+
**If the combined roster is empty** (`{roster}.empty` is true):
|
|
93
63
|
|
|
94
|
-
|
|
64
|
+
- When a skill name was supplied as an argument and it is in `{not_offered}`, take the §4 refusal for that folder first, in either mode: display its message and HALT (exit code 5, `halt_reason: "not-skf-output"`, phase `select:roster`), with `skill: "{name}"` in the §1 halt envelope.
|
|
65
|
+
- Otherwise 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." When `{not_offered}` is non-empty, append: "Left untouched (not SKF output): {not_offered}." HALT (exit code 3, `halt_reason: "nothing-to-drop"`, phase `select:roster`) with the §1 halt envelope.
|
|
95
66
|
|
|
96
|
-
|
|
67
|
+
When a `skill_name` argument was supplied, show no list: §4 takes the name. Otherwise display the combined list, each skill's versions in the roster's order, each with its `status` ("on disk only" when its `in_manifest` is false) and a trailing `*` on the one whose `active` is true; a `purge_only` skill shows "(not in manifest: purge only)" in place of its versions. When `{not_offered}` is non-empty, show one line under the list: "Not offered (not SKF output): {not_offered}".
|
|
97
68
|
|
|
98
69
|
```
|
|
99
70
|
**Drop Skill — select target**
|
|
@@ -105,27 +76,34 @@ Available skills:
|
|
|
105
76
|
- 0.1.0 (deprecated)
|
|
106
77
|
2. express
|
|
107
78
|
- 4.18.0 (active) *
|
|
108
|
-
3. legacy-helper (not in manifest
|
|
109
|
-
```
|
|
79
|
+
3. legacy-helper (not in manifest: purge only)
|
|
110
80
|
|
|
111
|
-
|
|
81
|
+
Not offered (not SKF output): my-module-skill
|
|
82
|
+
```
|
|
112
83
|
|
|
113
84
|
### 4. Ask Which Skill
|
|
114
85
|
|
|
86
|
+
**GATE [default: use args]:** when a `skill_name` argument was supplied, take it as the answer below in either mode, without showing the prompt; the two checks that follow still apply, and an interactive run whose supplied name fails one runs §2's call again without `--skill` and shows §3's list before it asks. When none was supplied, headless mode HALTs (exit code 2, `halt_reason: "input-missing"`, phase `select:skill`) with the §1 halt envelope: "headless mode requires skill name argument." Interactive mode asks:
|
|
87
|
+
|
|
115
88
|
"**Which skill would you like to drop?**
|
|
116
89
|
Enter the skill name or its number from the list above, or `cancel` / `exit` / `:q` to abort."
|
|
117
90
|
|
|
118
|
-
Wait for user input. Accept either the numeric index or the skill name (exact match).
|
|
91
|
+
Wait for user input. Accept either the numeric index or the skill name (exact match).
|
|
119
92
|
|
|
93
|
+
- **If the input names a folder in `{not_offered}`:** display "**`{name}` is not SKF output: nothing was changed.** `{skills_output_folder}/{name}/` has no SKF marker in its `metadata.json`, so SKF will not delete it. A shared `{skills_output_folder}` is supported: SKF leaves the skills it did not generate alone, so manage `{name}` yourself (to remove it, delete that folder). Only if `{skills_output_folder}` holds a module's own source rather than skills, set `skills_output_folder` in `{project-root}/_bmad/skf/config.yaml` to a folder of its own, move your SKF skills there and re-run `/skf-setup`." When that folder's `{roster}.not_offered` entry has a non-empty `errors` list (for example, the folder is a link), show those errors in place of the marker sentence.
|
|
94
|
+
- **Interactive:** Re-display the list and ask again.
|
|
95
|
+
- **Headless (`{headless_mode}` is true):** HALT (exit code 5, `halt_reason: "not-skf-output"`, phase `select:skill`), with `skill: "{name}"` in the §1 halt envelope.
|
|
120
96
|
- **If the user's input does not match any listed skill:**
|
|
121
97
|
- **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
|
|
98
|
+
- **Headless (`{headless_mode}` is true):** the supplied `skill_name` argument resolves to no skill in the combined list, and there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`, phase `select:skill`) with the §1 halt envelope: "headless mode: skill argument `{supplied value}` does not match any listed skill."
|
|
123
99
|
|
|
124
|
-
Store the selection as `target_skill
|
|
100
|
+
Store the selection as `target_skill`, and `target_in_manifest` ← its `{roster}.skills` entry's `in_manifest`: the sections below restrict the drop options of a skill the manifest does not list.
|
|
125
101
|
|
|
126
102
|
### 5. Display Version Details
|
|
127
103
|
|
|
128
|
-
|
|
104
|
+
Bind `{version_rows}` ← the `versions` of `{target_skill}`'s roster entry (the manifest's and the on-disk versions, newest first, each with its manifest `status`, `in_manifest`, `on_disk`, `last_exported` and `ides`) and `{version_counts}` ← its `counts` (`non_deprecated` counts the manifest versions whose `status` is not `"deprecated"`, and `on_disk` the version folders); §6 to §9b read them.
|
|
105
|
+
|
|
106
|
+
**If `target_in_manifest = true`**, display every `{version_rows}` entry with `in_manifest` true:
|
|
129
107
|
|
|
130
108
|
```
|
|
131
109
|
**{target_skill} — versions:**
|
|
@@ -142,16 +120,18 @@ Store the selection as `target_skill`. Also store `target_in_manifest = true` if
|
|
|
142
120
|
```
|
|
143
121
|
**{target_skill} — on-disk versions (not in manifest):**
|
|
144
122
|
|
|
145
|
-
{
|
|
123
|
+
{each `{version_rows}` entry whose `on_disk` is true, newest first, or "(flat layout)" when the roster entry's `layout` is "flat"}
|
|
146
124
|
|
|
147
|
-
**Note:** This skill has no manifest entry, so soft-deprecate is not available. Only a skill-level hard purge can be performed — the drop will delete the entire on-disk skill group and forge group.
|
|
125
|
+
**Note:** This skill has no manifest entry, so soft-deprecate is not available. Only a skill-level hard purge can be performed — the drop will delete the entire on-disk skill group and forge group (a forge folder SKF did not generate stays where it is).
|
|
148
126
|
```
|
|
149
127
|
|
|
150
128
|
### 6. Ask Scope
|
|
151
129
|
|
|
152
|
-
**
|
|
130
|
+
**GATE [default: use args]:** the `version` argument answers this section in either mode, so a supplied value is never asked for again.
|
|
153
131
|
|
|
154
|
-
**If `target_in_manifest = true
|
|
132
|
+
**If `target_in_manifest = false`:** a draft skill can only be dropped as a whole. A `version` argument other than `all` asks for less, and the drop never widens it to every version: HALT (exit code 2, `halt_reason: "input-invalid"`, phase `select:scope`) in either mode, with `skill: "{target_skill}"` in the §1 halt envelope: "`{target_skill}` has no manifest entry, so only the whole skill can be dropped. Re-run with `version=all`." Otherwise (no `version` argument, or `all`), skip the prompt: set `target_versions = "all"` and `is_skill_level = true`, then proceed to section 7.
|
|
133
|
+
|
|
134
|
+
**If `target_in_manifest = true`:** a `version` argument of `all` takes **[A]** below, and any other value takes **[N]** with that value as the answer to "Which version?", without showing either prompt. With no `version` argument, headless mode HALTs (exit code 2, `halt_reason: "input-missing"`, phase `select:scope`), with `skill: "{target_skill}"` in the §1 halt envelope, rather than guess between one version and the whole skill: "headless mode requires a `version` argument for `{target_skill}`: `all`, or one of its versions ({each `{version_rows}` entry with `in_manifest` true})." Interactive mode asks:
|
|
155
135
|
|
|
156
136
|
"**Drop which version(s)?**
|
|
157
137
|
|
|
@@ -165,10 +145,10 @@ Wait for user selection.
|
|
|
165
145
|
|
|
166
146
|
"**Which version?** Enter the version string (e.g. `0.5.0`)."
|
|
167
147
|
|
|
168
|
-
Wait for user input. Validate that the version
|
|
148
|
+
Wait for user input (a supplied `version` argument is the input). Validate that the version is a `{version_rows}` entry with `in_manifest` true.
|
|
169
149
|
|
|
170
150
|
- **If it does not match (interactive):** repeat the prompt.
|
|
171
|
-
- **If it does not match (headless
|
|
151
|
+
- **If it does not match (headless, `{headless_mode}` is true):** the supplied `version` argument is not a `{version_rows}` entry with `in_manifest` true, and there is no further input to re-prompt for. HALT (exit code 2, `halt_reason: "input-invalid"`, phase `select:scope`), with `skill: "{target_skill}"` in the §1 halt envelope: "headless mode: version argument `{supplied value}` does not exist in `{target_skill}`'s versions."
|
|
172
152
|
|
|
173
153
|
Set `target_versions = [<selected version>]` and `is_skill_level = false`.
|
|
174
154
|
|
|
@@ -182,87 +162,107 @@ Set `target_versions = "all"` and `is_skill_level = true`.
|
|
|
182
162
|
|
|
183
163
|
**Applies only when `target_in_manifest = true` AND `is_skill_level = false` (specific version selected):**
|
|
184
164
|
|
|
185
|
-
1. Read the selected version's `status`
|
|
165
|
+
1. Read the selected version's `status` from its `{version_rows}` entry
|
|
186
166
|
2. If `status != "active"` → skip this guard, the version is safe to drop
|
|
187
167
|
3. If `status == "active"`:
|
|
188
|
-
a.
|
|
189
|
-
b. If that count is
|
|
168
|
+
a. Read `{version_counts}.non_deprecated` (§5): the manifest versions of `{target_skill}` whose `status` is not `"deprecated"` (`active`, `archived` or `draft`), this one included. The guard never counts by hand.
|
|
169
|
+
b. If that count is above `1` → REFUSE the drop:
|
|
190
170
|
|
|
191
171
|
"**Cannot drop the active version `{version}`.**
|
|
192
172
|
Other non-deprecated versions of `{target_skill}` still exist. To proceed, either:
|
|
193
173
|
|
|
194
|
-
**(a)**
|
|
174
|
+
**(a)** Make the version you keep active first: point the `active` link at it (`uv run {atomicWriteHelper} flip-link --link "{skills_output_folder}/{target_skill}/active" --target <version to keep>`), run `[EX] Export Skill` for `{target_skill}`, which exports that version and archives `{version}`, then return here to drop `{version}`, OR
|
|
195
175
|
|
|
196
176
|
**(b)** Use the `[A] All versions` option to drop every version of `{target_skill}` at once."
|
|
197
177
|
|
|
198
|
-
|
|
178
|
+
In (a), `<version to keep>` is the first `{version_rows}` entry other than `{version}` whose `in_manifest` and `on_disk` are true and whose `status` is not `"deprecated"` (the rows are newest first); with no such entry, show `<version to keep>` as written. `{atomicWriteHelper}` is the first existing path in `{atomicWriteProbeOrder}`, or its first entry when neither exists.
|
|
199
179
|
|
|
200
|
-
|
|
180
|
+
HALT (exit code 5, `halt_reason: "active-version-guard-refused"`, phase `select:active-version-guard`), with `skill: "{target_skill}"` and `versions_affected: ["{version}"]` in the §1 halt envelope. Do not proceed.
|
|
181
|
+
|
|
182
|
+
c. Otherwise (a count of `1`, or no other version in the manifest) → the active version is the ONLY non-deprecated version; allow the drop to continue (it is functionally equivalent to a skill-level drop on a single-version skill)
|
|
201
183
|
|
|
202
184
|
### 8. Ask Mode
|
|
203
185
|
|
|
204
|
-
**
|
|
186
|
+
**GATE [default: use args]:** a `mode` argument answers this section in either mode, and an interactive run with none asks; the cases below say when the run HALTs instead. When `{headless_mode}` is true, record the `drop_mode` this section sets as an auto-decision before §8b runs: stage `{run_dir}/decision.json` as `{"gate": "select.mode", "default_action": "use args", "taken_action": "<drop_mode>", "reason": "<mode_source>"}` and run
|
|
205
187
|
|
|
206
|
-
|
|
188
|
+
```bash
|
|
189
|
+
uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --decision < "{run_dir}/decision.json"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
The emitter folds each recorded decision into the result record's `headless_decisions`, and §10 records its auto-confirm the same way. If the command fails, go on: only that entry is lost.
|
|
193
|
+
|
|
194
|
+
**If `target_in_manifest = false`:** Skip this prompt — soft-deprecate is meaningless without a manifest entry to mark, so only a purge applies. Take the first case that matches:
|
|
207
195
|
|
|
208
|
-
|
|
196
|
+
1. A `mode` argument other than `purge`: HALT (exit code 2, `halt_reason: "input-invalid"`, phase `select:mode`), with `skill: "{target_skill}"` in the §1 halt envelope: "`{target_skill}` has no manifest entry, so there is nothing to deprecate. Re-run with `--mode purge` to delete it."
|
|
197
|
+
2. `{headless_mode}` is true and no `mode` argument was passed: HALT (exit code 2, `halt_reason: "input-missing"`, phase `select:mode`), with `skill: "{target_skill}"` in the §1 halt envelope: "headless mode: `{target_skill}` has no manifest entry, so only a purge applies; re-run with `--mode purge`."
|
|
198
|
+
3. Otherwise force `drop_mode = "purge"` (record `mode_source = "draft-skill-forced-purge"`), 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.", and apply §8b. An interactive run still confirms the purge at §10. A headless run reaches this case only with `mode=purge`, which the On-Activation guard has already checked against `{forbidPurgeInHeadless}`.
|
|
209
199
|
|
|
210
|
-
**
|
|
200
|
+
**If `target_in_manifest = true`:**
|
|
201
|
+
|
|
202
|
+
**If a `mode` argument was supplied at invocation:** 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"`, phase `select:mode`), with `skill: "{target_skill}"` in the §1 halt envelope: "invalid `--mode` value `{supplied}`: expected `deprecate` or `purge`."
|
|
211
203
|
|
|
212
|
-
**Otherwise
|
|
204
|
+
**Otherwise:** If `{headless_mode}` is true (no `mode` arg), there is no input to prompt for: HALT (exit code 2, `halt_reason: "input-missing"`, phase `select:mode`), with `skill: "{target_skill}"` in the §1 halt envelope: "headless mode requires `--mode deprecate|purge` to set the drop mode." Otherwise, prompt the user. Before showing the menu, run the §8b purge check at the current scope; when `{purge_verdict}` is not `"ok"`, leave out **[P]** and add the line "Purge is not offered: {the §8b refusal message for `{purge_reason}`}."
|
|
213
205
|
|
|
214
206
|
"**How should this be dropped?**
|
|
215
207
|
|
|
216
|
-
- **[D]** Deprecate (soft)
|
|
217
|
-
- **[P]** Purge (hard)
|
|
208
|
+
- **[D]** Deprecate (soft): mark the version as `deprecated` in the manifest. Files remain on disk. Export-skill will exclude it from all platform context files. Reversible by editing the manifest.
|
|
209
|
+
- **[P]** Purge (hard): deprecate and delete {affected_directories} from disk (a forge folder SKF did not generate stays where it is). **Irreversible.**
|
|
218
210
|
- **[X]** Cancel and exit (or type `cancel` / `exit` / `:q`)"
|
|
219
211
|
|
|
220
212
|
Wait for user selection.
|
|
221
213
|
|
|
222
214
|
Set `drop_mode` to `"deprecate"` (on D) or `"purge"` (on P), and record `mode_source = "interactive-prompt"`.
|
|
223
215
|
|
|
224
|
-
###
|
|
216
|
+
### 8b. Purge Guard
|
|
225
217
|
|
|
226
|
-
|
|
218
|
+
A purge deletes only what SKF generated, and the inventory helper decides what that is: never decide by hand whether SKF generated a folder.
|
|
227
219
|
|
|
228
|
-
**
|
|
220
|
+
**The purge check.** Run it once, at the scope §6 chose, and reuse its result wherever this step reads it (§8's menu, the guard below and §9). Resolve `{skillInventoryHelper}` ← first existing path in `{skillInventoryProbeOrder}`:
|
|
229
221
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
222
|
+
```bash
|
|
223
|
+
uv run {skillInventoryHelper} "{skills_output_folder}" --skill {target_skill} --purge-check [--purge-version {version}] --forge-data-folder "{forge_data_folder}"
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Pass `--purge-version {version}` for a single-version drop (`is_skill_level = false`) and leave it out for a whole skill. From its `purge_check`, bind `{purge_verdict}` ← `verdict`, `{purge_reason}` ← `reason`, `{purge_detail}` ← `detail`, `{purge_entries}` ← `offending_entries`, `{affected_directories}` ← `affected_directories`, `{forge_left_in_place}` ← `forge_left_in_place` and `{forge_errors}` ← `forge_errors`. The helper alone decides what a purge may delete: `{purge_reason}` names the rule that refused, and `{forge_left_in_place}` the forge folder a purge leaves where it is because SKF did not generate it.
|
|
233
227
|
|
|
234
|
-
|
|
228
|
+
When `{skillInventoryHelper}` resolves to no path, `{roster}.inventory` is false, the call exits non-zero, or its result has no `purge_check` (an installed `skf-skill-inventory.py` older than the flag), bind `{purge_verdict}` and `{purge_reason}` to `"unknown"`: SKF cannot check what it would delete.
|
|
235
229
|
|
|
236
|
-
|
|
237
|
-
- `{forge_group}` = `{forge_data_folder}/{target_skill}/`
|
|
230
|
+
**The guard.** Skip it when `drop_mode == "deprecate"`. When `{purge_verdict}` is not `"ok"`, HALT (exit code 5, `halt_reason: "not-skf-output"`, phase `select:purge-guard`) with the message for `{purge_reason}`, and `skill: "{target_skill}"` and `drop_mode: "purge"` in the §1 halt envelope.
|
|
238
231
|
|
|
239
|
-
|
|
232
|
+
- `skill-mixed-whole`: "**Purge refused: `{skills_output_folder}/{target_skill}/` also holds entries SKF did not generate:** {purge_entries}. Nothing was deleted. Move them out of the folder and re-run. For a skill in the manifest, you can also purge a single version SKF generated, or use `--mode deprecate`. A version folder with no `metadata.json` can also be one that an interrupted update-skill run left behind; delete it yourself in that case."
|
|
233
|
+
- `skill-version-not-skf`: "**Purge refused: SKF did not generate `{skills_output_folder}/{target_skill}/{version}`** (it has no SKF marker in its `metadata.json`, or it is a link). Nothing was deleted. Use `--mode deprecate` to mark the version deprecated in the manifest only, or remove it yourself."
|
|
234
|
+
- `skill-version-mixed`: "**Purge refused: `{skills_output_folder}/{target_skill}/{version}` also holds entries SKF did not generate:** {purge_entries}. Nothing was deleted. Move them out and re-run, or use `--mode deprecate`."
|
|
235
|
+
- `skill-foreign`, `reserved-name` or `unknown`: "**Purge refused: SKF cannot confirm that it generated `{skills_output_folder}/{target_skill}/`:** {reason}. Nothing was deleted. Use `--mode deprecate` to mark it deprecated in the manifest only, or delete the folder yourself." `{reason}` is `{purge_detail}` (it names a link, or a `metadata.json` with no SKF marker), and for `unknown` "the inventory helper is missing, or the installed `skf-skill-inventory.py` has no purge check; re-install SKF".
|
|
236
|
+
- `forge-mixed-whole`: "**Purge refused: `{forge_data_folder}/{target_skill}/` also holds entries SKF did not generate:** {purge_entries}. Nothing was deleted. Move them out of the folder and re-run. For a skill in the manifest, you can also purge a single version SKF generated, or use `--mode deprecate`."
|
|
237
|
+
- `forge-version-mixed`: "**Purge refused: `{forge_data_folder}/{target_skill}/{version}/` holds entries SKF did not generate:** {purge_entries}. Nothing was deleted. Move them out and re-run, or use `--mode deprecate`."
|
|
240
238
|
|
|
241
|
-
|
|
239
|
+
### 9. Compute Affected Directories
|
|
240
|
+
|
|
241
|
+
`affected_directories` is the §8b purge check's list: the folders a purge at this scope deletes. In deprecate mode the list shows what the drop keeps on disk, and §10 presents it as retained. When the purge check has no verdict (`"unknown"`), build it from these paths instead: `{skills_output_folder}/{target_skill}/{version}` and `{forge_data_folder}/{target_skill}/{version}` for a single version, `{skills_output_folder}/{target_skill}` and `{forge_data_folder}/{target_skill}` for a whole skill, each without a trailing `/`, and only once when both settings name one folder.
|
|
242
242
|
|
|
243
243
|
#### 9b. Compute Blast-Radius Metrics (for §10 summary)
|
|
244
244
|
|
|
245
|
-
Compute three scalars
|
|
245
|
+
Compute the three scalars §10's `Scope:` line shows:
|
|
246
246
|
|
|
247
|
-
1. **`versions_count
|
|
248
|
-
- Version-level drop: `
|
|
249
|
-
- Skill-level drop:
|
|
247
|
+
1. **`versions_count`**: the number of skill versions in scope, never counted by hand:
|
|
248
|
+
- Version-level drop: `1`
|
|
249
|
+
- Skill-level drop: `{version_counts}.non_deprecated` (§5) for a skill in the manifest (the deprecated versions are already absent from the active managed sections), and `{version_counts}.on_disk` for a draft
|
|
250
250
|
|
|
251
|
-
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
252
|
|
|
253
253
|
```bash
|
|
254
|
-
uv run {dirSizesHelper} sizes {each path in affected_directories, space-separated}
|
|
254
|
+
uv run {dirSizesHelper} sizes {each path in affected_directories, quoted, space-separated}
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
Read `total_human` (e.g. `"4.2 MB"`) as `bytes_total
|
|
257
|
+
Read `total_human` (e.g. `"4.2 MB"`) as `bytes_total`; a path that does not exist adds nothing. If the helper is unavailable, fall back to `du -sb` per path: the display is best-effort.
|
|
258
258
|
|
|
259
|
-
3. **`context_files_count
|
|
260
|
-
- Read `config.yaml.ides`
|
|
261
|
-
- For each entry, look up its `context_file` via the canonical mapping table in `skf-export-skill/assets/managed-section-format.md` (use the `{unknownIdeDefaultContextFile}` fallback for unknown IDEs)
|
|
262
|
-
- Deduplicate by `context_file`
|
|
263
|
-
- Count the result. If `config.yaml.ides` is absent or empty, default to `1` (the single `{unknownIdeDefaultContextFile}` fallback)
|
|
259
|
+
3. **`context_files_count`**: the number of context files step 2 rebuilds. Map the `ides` list of `config.yaml` (an absent key is an empty list) through the IDE mapping export-skill writes the section with, using the `{rebuildManagedSectionsHelper}` On-Activation §4 resolved:
|
|
264
260
|
|
|
265
|
-
|
|
261
|
+
```bash
|
|
262
|
+
python3 {rebuildManagedSectionsHelper} resolve-targets --ides "{ides}"
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`{ides}` is the comma-joined list. Store `targets` as `target_context_files` (one `{context_file, skill_root, ides}` entry per file; an IDE the mapping does not list, and an empty list, resolve to AGENTS.md with `.agents/skills/`), set `context_files_count` to its length, and show each `warnings[]` and `notes[]` line. When the helper exits non-zero, HALT (exit code 4, `halt_reason: "context-rebuild-failed"`, phase `select:context-files`), with `skill: "{target_skill}"` and `drop_mode: "{drop_mode}"` in the §1 halt envelope: "SKF cannot resolve the context files to rebuild: {the helper's `error`, or its stderr when stdout holds no JSON}. Nothing was changed. Re-install SKF."
|
|
266
266
|
|
|
267
267
|
### 10. Confirmation Gate
|
|
268
268
|
|
|
@@ -278,6 +278,7 @@ Display the full operation summary with the blast-radius summary line ahead of t
|
|
|
278
278
|
Files:
|
|
279
279
|
{for each path in affected_directories, list one per line}
|
|
280
280
|
{or "(retained on disk — soft drop)" if drop_mode == "deprecate"}
|
|
281
|
+
{if forge_left_in_place:} Left in place (not SKF output): {forge_left_in_place}{if forge_errors is non-empty: ": " + forge_errors}
|
|
281
282
|
|
|
282
283
|
{if drop_mode == "purge":}
|
|
283
284
|
⚠️ This operation cannot be undone. Files will be permanently deleted.
|
|
@@ -287,11 +288,7 @@ Display the full operation summary with the blast-radius summary line ahead of t
|
|
|
287
288
|
Proceed? [Y/N]
|
|
288
289
|
```
|
|
289
290
|
|
|
290
|
-
|
|
291
|
-
|
|
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.
|
|
293
|
-
|
|
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:
|
|
291
|
+
**If `--dry-run` was passed**, it takes precedence over the headless auto-confirm: skip the Y/N prompt even when `{headless_mode}` is true, change nothing, and display the `[DRY RUN]` line with the resolved selection, so the user can re-run with the same values when ready to commit:
|
|
295
292
|
|
|
296
293
|
```
|
|
297
294
|
**[DRY RUN] No changes were made — preview above shows what would be dropped.**
|
|
@@ -302,28 +299,29 @@ Resolved selection:
|
|
|
302
299
|
Mode: {Deprecate (soft) | Purge (hard)}
|
|
303
300
|
```
|
|
304
301
|
|
|
305
|
-
|
|
302
|
+
When `{headless_mode}` is true, stage the preview as `{run_dir}/result-context.json` and print its envelope. `would_delete` lists the folders a purge would delete, so an automator can check the blast radius before it runs the drop:
|
|
306
303
|
|
|
307
|
-
|
|
304
|
+
```json
|
|
305
|
+
{"status": "dry-run", "skill": "{target_skill}", "drop_mode": "{drop_mode}", "versions_affected": {target_versions}, "would_delete": {affected_directories when drop_mode is purge, else []}, "forge_left_in_place": {forge_left_in_place when drop_mode is purge, else null}}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
uv run {emitEnvelopeHelper} emit --workflow skf-drop-skill --run-dir "{run_dir}" < "{run_dir}/result-context.json"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
A dry run passes no `--result-dir`, so it writes no result file. Then, in either mode, delete the run folder (`rm -rf "{run_dir}"`) and HALT (exit code 0). In a headless run, display the line the emitter printed verbatim (the `message` of its stderr JSON when it exits non-zero) after the delete and before the HALT, with nothing after it, so a `claude -p` caller reads it as the final message. The manifest, filesystem, and context files are untouched.
|
|
313
|
+
|
|
314
|
+
**Otherwise (not a dry-run), GATE [default: Y]:** If `{headless_mode}`: auto-proceed with [Y], record `confirm_source = "headless-auto"`, stage `{run_dir}/decision.json` as `{"gate": "select.confirm", "default_action": "Y", "taken_action": "Y", "reason": "headless auto-confirm"}` and run the §8 `record` command, and log: "headless: auto-confirmed drop of {target_skill}"
|
|
308
315
|
|
|
309
316
|
Wait for explicit user response.
|
|
310
317
|
|
|
311
318
|
- **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"
|
|
319
|
+
- **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"`, phase `select:confirm`).
|
|
313
320
|
- **Any other input** → re-display the confirmation and ask again
|
|
314
321
|
|
|
315
322
|
### 11. Store Decisions in Context
|
|
316
323
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
- `target_skill` — the skill name
|
|
320
|
-
- `target_in_manifest` — boolean (true if the skill has a manifest entry, false if it was discovered only by on-disk scan)
|
|
321
|
-
- `target_versions` — list of version strings (`[<version>]`) or the literal string `"all"`
|
|
322
|
-
- `drop_mode` — `"deprecate"` or `"purge"` (always `"purge"` when `target_in_manifest = false`)
|
|
323
|
-
- `is_skill_level` — boolean (true if all versions; always true when `target_in_manifest = false`)
|
|
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"`)
|
|
324
|
+
Step 2 reads the decisions this step bound where it made them: `target_skill`, `target_in_manifest`, `target_versions`, `drop_mode`, `is_skill_level`, `affected_directories`, `mode_source`, `forge_left_in_place`, `target_context_files` and `confirm_source`.
|
|
327
325
|
|
|
328
326
|
### 12. Load Next Step
|
|
329
327
|
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
5
|
"""Deterministic directory-size measurement for skf-drop-skill.
|
|
6
6
|
|
|
7
|
-
The drop workflow twice needs
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
The drop workflow twice needs a stable human label ("4.2 MB" / "812 KB"):
|
|
8
|
+
select.md section 9b renders the blast-radius line ahead of the confirmation
|
|
9
|
+
gate from the exact recursive byte total measured here, and execute.md
|
|
10
|
+
section 4 formats the canonical `disk_freed` from the `bytes_freed` that
|
|
11
|
+
skf-skill-inventory.py guarded-delete measured just before it deleted each
|
|
12
|
+
folder. Summing file sizes and rounding a unit in the prompt has one correct
|
|
13
|
+
answer per input, so it belongs here: identical input yields identical
|
|
14
|
+
output, and the gate preview and the post-purge report agree on the method
|
|
15
|
+
(they can still differ only because they read at different times).
|
|
14
16
|
|
|
15
17
|
Two operations:
|
|
16
18
|
|
|
@@ -19,14 +21,16 @@ Two operations:
|
|
|
19
21
|
excluded from the total). Symlinks are measured by the
|
|
20
22
|
size of the link itself, never followed.
|
|
21
23
|
humanize <bytes>... sum a set of already-measured byte counts and format
|
|
22
|
-
one human label. execute.md
|
|
23
|
-
|
|
24
|
+
one human label. execute.md section 4 feeds it the
|
|
25
|
+
`bytes_freed` of guarded-delete.
|
|
24
26
|
|
|
25
|
-
Output is a single JSON object on stdout. Exit codes: 0 ok
|
|
26
|
-
(missing/unknown op, or a non-integer byte count
|
|
27
|
+
Output is a single JSON object on stdout. Exit codes: 0 ok (and --help);
|
|
28
|
+
2 usage error (missing/unknown op or argument, or a non-integer byte count
|
|
29
|
+
for `humanize`), reported as a JSON error on stderr.
|
|
27
30
|
"""
|
|
28
31
|
from __future__ import annotations
|
|
29
32
|
|
|
33
|
+
import argparse
|
|
30
34
|
import json
|
|
31
35
|
import os
|
|
32
36
|
import sys
|
|
@@ -97,20 +101,44 @@ def _op_humanize(raw: list[str]) -> dict:
|
|
|
97
101
|
return {"status": "ok", "op": "humanize", "total_bytes": total, "total_human": humanize_bytes(total)}
|
|
98
102
|
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
104
|
+
class _JsonErrorParser(argparse.ArgumentParser):
|
|
105
|
+
"""Report a usage error as the same stderr JSON and exit 2 as a bad byte count.
|
|
106
|
+
|
|
107
|
+
The subcommand parsers are made with this class too, so an unknown op or
|
|
108
|
+
flag never reaches argparse's own plain-text usage.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def error(self, message: str) -> None:
|
|
112
|
+
print(json.dumps({"status": "error", "error": f"usage error: {self.prog}: {message}"}), file=sys.stderr)
|
|
113
|
+
sys.exit(2)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
117
|
+
parser = _JsonErrorParser(
|
|
118
|
+
prog="dir-sizes.py",
|
|
119
|
+
description=__doc__,
|
|
120
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
121
|
+
)
|
|
122
|
+
sub = parser.add_subparsers(dest="op", required=True, metavar="{sizes,humanize}")
|
|
123
|
+
|
|
124
|
+
p_sizes = sub.add_parser("sizes", help="recursive byte size of each path, and their total")
|
|
125
|
+
p_sizes.add_argument("paths", nargs="*", metavar="path",
|
|
126
|
+
help="a file or folder to measure; a link is measured as itself, never followed")
|
|
127
|
+
|
|
128
|
+
p_humanize = sub.add_parser("humanize", help="sum already-measured byte counts into one human label")
|
|
129
|
+
p_humanize.add_argument("byte_counts", nargs="*", metavar="bytes",
|
|
130
|
+
help="an integer byte count, such as guarded-delete's bytes_freed")
|
|
131
|
+
return parser
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def main(argv: list[str] | None = None) -> int:
|
|
135
|
+
args = _build_parser().parse_args(argv)
|
|
136
|
+
if args.op == "sizes":
|
|
137
|
+
print(json.dumps(_op_sizes(args.paths)))
|
|
138
|
+
else:
|
|
139
|
+
print(json.dumps(_op_humanize(args.byte_counts)))
|
|
140
|
+
return 0
|
|
113
141
|
|
|
114
142
|
|
|
115
143
|
if __name__ == "__main__":
|
|
116
|
-
sys.exit(main(
|
|
144
|
+
sys.exit(main())
|