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
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""Drop Skill roster: the two lists a drop reads, each from one call.
|
|
7
|
+
|
|
8
|
+
skills <skills-folder> --forge-data-folder <path> [--skill <name>]
|
|
9
|
+
select.md section 2: every skill the drop can offer (section 3 lists
|
|
10
|
+
it). It joins the export manifest, read through skf-manifest-ops.py
|
|
11
|
+
(which migrates a v1 manifest), with the on-disk scan of
|
|
12
|
+
skf-skill-inventory.py: each manifest skill, and each skill SKF
|
|
13
|
+
generated that the manifest does not list (a draft, which only a purge
|
|
14
|
+
can drop). A folder holding a skill SKF did not generate is never
|
|
15
|
+
offered: it is listed in `not_offered`, with the scan's errors for it.
|
|
16
|
+
Each skill carries its versions newest first, in the order
|
|
17
|
+
skf-skill-inventory.py resolve gives them (numeric, so 0.10.0 comes
|
|
18
|
+
before 0.9.0), each with its manifest status, last export date and
|
|
19
|
+
IDEs, and the counts the active-version guard and the blast-radius
|
|
20
|
+
line read. With --skill the roster holds that skill only, so a run
|
|
21
|
+
given its target reads one skill rather than every one; `empty` and
|
|
22
|
+
`not_offered` still cover the whole folder.
|
|
23
|
+
|
|
24
|
+
Without skf-skill-inventory.py (an older or broken install), or when
|
|
25
|
+
its scan or resolve fails, `inventory` is false and the roster holds
|
|
26
|
+
the manifest skills alone: SKF cannot check that it generated a folder
|
|
27
|
+
the manifest does not list, so none is offered.
|
|
28
|
+
|
|
29
|
+
rows --skill <name> [--version <v>]... <context-file>...
|
|
30
|
+
execute.md section 5: the managed-section rows the context files still
|
|
31
|
+
hold for a skill (for those versions only, with --version), as
|
|
32
|
+
{file, version}, read with the parser skf-rebuild-managed-sections.py
|
|
33
|
+
writes the section with. A row's snippet text never comes back. A file
|
|
34
|
+
whose markers are malformed, or that cannot be read, is listed in
|
|
35
|
+
`unchecked` with the reason: its rows are unknown. A missing file holds
|
|
36
|
+
no row.
|
|
37
|
+
|
|
38
|
+
Both shared scripts are read from the shared scripts folder beside this
|
|
39
|
+
skill's folder (shared/scripts/), where the installer puts them in an
|
|
40
|
+
installed project as in this checkout.
|
|
41
|
+
|
|
42
|
+
Output (stdout, one JSON object):
|
|
43
|
+
|
|
44
|
+
skills: {"status": "ok", "inventory": bool, "inventory_error": str|null,
|
|
45
|
+
"skills": [{"name", "in_manifest", "purge_only", "active_version",
|
|
46
|
+
"layout": "versioned"|"flat"|"none"|null,
|
|
47
|
+
"versions": [{"version", "status", "active",
|
|
48
|
+
"in_manifest", "on_disk",
|
|
49
|
+
"last_exported", "ides"}],
|
|
50
|
+
"counts": {"non_deprecated", "on_disk"}}],
|
|
51
|
+
"not_offered": [{"name", "errors"}], "empty": bool}
|
|
52
|
+
rows: {"status": "ok", "skill", "versions": [...]|null,
|
|
53
|
+
"rows": [{"file", "version"}], "unchecked": [{"file", "error"}]}
|
|
54
|
+
|
|
55
|
+
`status` of a version is its manifest status, null for a folder the manifest
|
|
56
|
+
does not list; `active` marks the manifest's active_version. `counts` gives
|
|
57
|
+
`non_deprecated` (the manifest versions whose status is not "deprecated")
|
|
58
|
+
and `on_disk` (the version folders; null without the inventory helper).
|
|
59
|
+
|
|
60
|
+
Exit codes:
|
|
61
|
+
0 status "ok"
|
|
62
|
+
1 status "error", with a `code`: manifest-corrupt (the export manifest
|
|
63
|
+
exists but does not parse, is not UTF-8 or is not an object of entry
|
|
64
|
+
objects; `path` names it) or helper-missing (a shared script this one
|
|
65
|
+
reads cannot be loaded)
|
|
66
|
+
2 usage error (JSON on stderr)
|
|
67
|
+
"""
|
|
68
|
+
from __future__ import annotations
|
|
69
|
+
|
|
70
|
+
import argparse
|
|
71
|
+
import importlib.util
|
|
72
|
+
import json
|
|
73
|
+
import sys
|
|
74
|
+
from pathlib import Path
|
|
75
|
+
|
|
76
|
+
# Installed (under _bmad/skf/ or an IDE's skills folder) and in a dev
|
|
77
|
+
# checkout (src/), shared/ sits beside this skill's folder.
|
|
78
|
+
SHARED_SCRIPTS = Path(__file__).resolve().parent.parent.parent / "shared" / "scripts"
|
|
79
|
+
MANIFEST_OPS = "skf-manifest-ops.py"
|
|
80
|
+
INVENTORY = "skf-skill-inventory.py"
|
|
81
|
+
REBUILD = "skf-rebuild-managed-sections.py"
|
|
82
|
+
MANIFEST_FILE = ".export-manifest.json"
|
|
83
|
+
# What a shared helper raises on a file it cannot read as it expects (a
|
|
84
|
+
# manifest of the wrong shape or encoding, a folder it cannot list).
|
|
85
|
+
HELPER_ERRORS = (AttributeError, TypeError, ValueError, OSError)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _load(filename: str):
|
|
89
|
+
"""(module, None) for a shared script, or (None, the reason it cannot load)."""
|
|
90
|
+
path = SHARED_SCRIPTS / filename
|
|
91
|
+
try:
|
|
92
|
+
spec = importlib.util.spec_from_file_location(filename[:-3].replace("-", "_"), path)
|
|
93
|
+
module = importlib.util.module_from_spec(spec)
|
|
94
|
+
spec.loader.exec_module(module)
|
|
95
|
+
except (ImportError, OSError, AttributeError, SyntaxError) as e:
|
|
96
|
+
return None, f"cannot load {filename} from {SHARED_SCRIPTS} ({e}): re-install SKF"
|
|
97
|
+
return module, None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _plain_name(value) -> bool:
|
|
101
|
+
"""True for a name that is one folder name and nothing else."""
|
|
102
|
+
return isinstance(value, str) and value not in ("", ".", "..") and not any(c in value for c in "/\\:\0")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _manifest_rows(entry) -> tuple[str | None, dict]:
|
|
106
|
+
"""(active_version, {version: {status, last_exported, ides}}) of a migrated manifest entry."""
|
|
107
|
+
if not isinstance(entry, dict):
|
|
108
|
+
return None, {}
|
|
109
|
+
active = entry.get("active_version")
|
|
110
|
+
active = active if isinstance(active, str) and active else None
|
|
111
|
+
versions = entry.get("versions")
|
|
112
|
+
records = {}
|
|
113
|
+
if isinstance(versions, dict):
|
|
114
|
+
for version, record in versions.items():
|
|
115
|
+
record = record if isinstance(record, dict) else {}
|
|
116
|
+
status = record.get("status")
|
|
117
|
+
last = record.get("last_exported")
|
|
118
|
+
ides = record.get("ides")
|
|
119
|
+
records[version] = {
|
|
120
|
+
"status": status if isinstance(status, str) else None,
|
|
121
|
+
"last_exported": last if isinstance(last, str) and last else None,
|
|
122
|
+
"ides": [i for i in ides if isinstance(i, str)] if isinstance(ides, list) else [],
|
|
123
|
+
}
|
|
124
|
+
return active, records
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _version_row(version: str, active: str | None, records: dict, on_disk: bool) -> dict:
|
|
128
|
+
record = records.get(version, {})
|
|
129
|
+
return {
|
|
130
|
+
"version": version,
|
|
131
|
+
"status": record.get("status"),
|
|
132
|
+
"active": version == active,
|
|
133
|
+
"in_manifest": version in records,
|
|
134
|
+
"on_disk": on_disk,
|
|
135
|
+
"last_exported": record.get("last_exported"),
|
|
136
|
+
"ides": record.get("ides", []),
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _skill(name, entry, ops, manifest_path, inventory, skills_folder, forge_folder) -> dict:
|
|
141
|
+
"""One roster entry: the skill's versions newest first, with their manifest records and counts."""
|
|
142
|
+
active, records = _manifest_rows(entry)
|
|
143
|
+
in_manifest = entry is not None
|
|
144
|
+
resolved = None
|
|
145
|
+
if inventory is not None and _plain_name(name):
|
|
146
|
+
resolved = inventory.resolve_skill(skills_folder, name, forge_folder)
|
|
147
|
+
if resolved is not None:
|
|
148
|
+
rows = [_version_row(r["version"], active, records, r["on_disk"]) for r in resolved["versions"]]
|
|
149
|
+
layout = resolved["layout"]
|
|
150
|
+
else:
|
|
151
|
+
# No on-disk scan: the manifest's versions, in the manifest helper's order.
|
|
152
|
+
ordered = ops.cmd_affected_versions(manifest_path, skills_folder, name).get("sources", {}).get("manifest", [])
|
|
153
|
+
rows = [_version_row(v, active, records, False) for v in ordered if v in records]
|
|
154
|
+
layout = None
|
|
155
|
+
return {
|
|
156
|
+
"name": name,
|
|
157
|
+
"in_manifest": in_manifest,
|
|
158
|
+
"purge_only": not in_manifest,
|
|
159
|
+
"active_version": active,
|
|
160
|
+
"layout": layout,
|
|
161
|
+
"versions": rows,
|
|
162
|
+
"counts": {
|
|
163
|
+
"non_deprecated": sum(1 for r in rows if r["in_manifest"] and r["status"] != "deprecated"),
|
|
164
|
+
"on_disk": sum(1 for r in rows if r["on_disk"]) if resolved is not None else None,
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _read_manifest(ops, manifest_path: Path) -> tuple[dict | None, str | None]:
|
|
170
|
+
"""(data, None), or (None, why the manifest cannot be read as one).
|
|
171
|
+
|
|
172
|
+
The manifest helper reports a file that is not JSON; a JSON value of the
|
|
173
|
+
wrong shape (a list, or an entry that is not an object) or a file that is
|
|
174
|
+
not UTF-8 makes it raise instead, which is the same corrupt manifest.
|
|
175
|
+
"""
|
|
176
|
+
try:
|
|
177
|
+
data, err = ops.read_manifest(manifest_path)
|
|
178
|
+
except HELPER_ERRORS as e:
|
|
179
|
+
return None, f"Manifest cannot be read: {type(e).__name__}: {e}"
|
|
180
|
+
if err:
|
|
181
|
+
return None, err
|
|
182
|
+
if not isinstance(data, dict) or not isinstance(data.get("exports", {}), dict):
|
|
183
|
+
return None, "Manifest is not an object with an `exports` object"
|
|
184
|
+
return data, None
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def roster(skills_folder: str, forge_folder: str, only: str | None = None) -> tuple[dict, int]:
|
|
188
|
+
"""The `skills` result and its exit code."""
|
|
189
|
+
ops, err = _load(MANIFEST_OPS)
|
|
190
|
+
if ops is None:
|
|
191
|
+
return {"status": "error", "code": "helper-missing", "error": err}, 1
|
|
192
|
+
manifest_path = Path(skills_folder) / MANIFEST_FILE
|
|
193
|
+
data, err = _read_manifest(ops, manifest_path)
|
|
194
|
+
if err:
|
|
195
|
+
return {"status": "error", "code": "manifest-corrupt", "error": err, "path": str(manifest_path)}, 1
|
|
196
|
+
exports = data.get("exports", {})
|
|
197
|
+
|
|
198
|
+
inventory, inventory_error = _load(INVENTORY)
|
|
199
|
+
drafts, not_offered = [], []
|
|
200
|
+
if inventory is not None:
|
|
201
|
+
try:
|
|
202
|
+
scan = inventory.scan_inventory(skills_folder, forge_data_folder=forge_folder)
|
|
203
|
+
except HELPER_ERRORS as e:
|
|
204
|
+
scan = {"status": "error", "error": f"the inventory scan failed: {type(e).__name__}: {e}"}
|
|
205
|
+
if scan.get("status") == "ok":
|
|
206
|
+
for found in scan["skills"]:
|
|
207
|
+
if found.get("skf_skill") and found["name"] not in exports:
|
|
208
|
+
drafts.append(found["name"])
|
|
209
|
+
errors = {found["name"]: found.get("errors", []) for found in scan["skills"]}
|
|
210
|
+
not_offered = [{"name": name, "errors": errors.get(name, [])}
|
|
211
|
+
for name in scan.get("not_skf_output", []) if name not in exports]
|
|
212
|
+
elif scan.get("code") != "DIR_NOT_FOUND": # a missing folder holds no skill
|
|
213
|
+
inventory, inventory_error = None, scan.get("error", "the inventory scan failed")
|
|
214
|
+
|
|
215
|
+
names = sorted(exports) + sorted(drafts)
|
|
216
|
+
listed = [n for n in names if n == only] if only is not None else names
|
|
217
|
+
try:
|
|
218
|
+
skills = [_skill(name, exports.get(name), ops, manifest_path, inventory, skills_folder, forge_folder)
|
|
219
|
+
for name in listed]
|
|
220
|
+
except HELPER_ERRORS as e:
|
|
221
|
+
if inventory is None:
|
|
222
|
+
raise
|
|
223
|
+
# A resolve that fails is a scan that failed: the manifest skills alone, as without the helper.
|
|
224
|
+
inventory, inventory_error = None, f"the inventory resolve failed: {type(e).__name__}: {e}"
|
|
225
|
+
drafts, not_offered = [], []
|
|
226
|
+
names = sorted(exports)
|
|
227
|
+
listed = [n for n in names if n == only] if only is not None else names
|
|
228
|
+
skills = [_skill(name, exports.get(name), ops, manifest_path, None, skills_folder, forge_folder)
|
|
229
|
+
for name in listed]
|
|
230
|
+
return {
|
|
231
|
+
"status": "ok",
|
|
232
|
+
"inventory": inventory is not None,
|
|
233
|
+
"inventory_error": None if inventory is not None else inventory_error,
|
|
234
|
+
"skills": skills,
|
|
235
|
+
"not_offered": not_offered,
|
|
236
|
+
"empty": not names,
|
|
237
|
+
}, 0
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def rows(skill: str, versions: list[str], context_files: list[str]) -> tuple[dict, int]:
|
|
241
|
+
"""The `rows` result and its exit code."""
|
|
242
|
+
rebuild, err = _load(REBUILD)
|
|
243
|
+
if rebuild is None:
|
|
244
|
+
return {"status": "error", "code": "helper-missing", "error": err}, 1
|
|
245
|
+
found, unchecked = [], []
|
|
246
|
+
for path in context_files:
|
|
247
|
+
content, err = rebuild.read_context_file(path)
|
|
248
|
+
if err:
|
|
249
|
+
if not rebuild.is_missing(err):
|
|
250
|
+
unchecked.append({"file": path, "error": err})
|
|
251
|
+
continue
|
|
252
|
+
problem = rebuild.find_unclosed_begin(content)
|
|
253
|
+
if problem:
|
|
254
|
+
unchecked.append({"file": path, "error": f"malformed markers: {problem[1]}"})
|
|
255
|
+
continue
|
|
256
|
+
match = rebuild.find_managed_section(content)
|
|
257
|
+
if not match:
|
|
258
|
+
continue
|
|
259
|
+
for row in rebuild.parse_managed_rows(match.group(2)):
|
|
260
|
+
if row["skill_name"] == skill and (not versions or row["version"] in versions):
|
|
261
|
+
found.append({"file": path, "version": row["version"]})
|
|
262
|
+
return {"status": "ok", "skill": skill, "versions": versions or None, "rows": found,
|
|
263
|
+
"unchecked": unchecked}, 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class _JsonErrorParser(argparse.ArgumentParser):
|
|
267
|
+
"""Report a usage error as JSON on stderr with exit 2, the subcommands included."""
|
|
268
|
+
|
|
269
|
+
def error(self, message: str) -> None:
|
|
270
|
+
print(json.dumps({"status": "error", "error": f"usage error: {self.prog}: {message}"}), file=sys.stderr)
|
|
271
|
+
sys.exit(2)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
275
|
+
parser = _JsonErrorParser(
|
|
276
|
+
prog="drop-roster.py",
|
|
277
|
+
description=__doc__,
|
|
278
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
279
|
+
)
|
|
280
|
+
sub = parser.add_subparsers(dest="op", required=True, metavar="{skills,rows}")
|
|
281
|
+
|
|
282
|
+
p_skills = sub.add_parser("skills", help="the skills a drop can offer, with their versions")
|
|
283
|
+
p_skills.add_argument("skills_folder", metavar="skills-folder", help="{skills_output_folder}")
|
|
284
|
+
p_skills.add_argument("--forge-data-folder", required=True, help="{forge_data_folder}")
|
|
285
|
+
p_skills.add_argument("--skill", help="list this skill only (the name the run was given)")
|
|
286
|
+
|
|
287
|
+
p_rows = sub.add_parser("rows", help="the managed-section rows the context files hold for a skill")
|
|
288
|
+
p_rows.add_argument("--skill", required=True, help="the skill whose rows to list")
|
|
289
|
+
p_rows.add_argument("--version", action="append", default=[], dest="versions",
|
|
290
|
+
help="list the rows of this version only (repeatable)")
|
|
291
|
+
p_rows.add_argument("context_files", nargs="+", metavar="context-file", help="a context file to read")
|
|
292
|
+
return parser
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def main(argv: list[str] | None = None) -> int:
|
|
296
|
+
args = _build_parser().parse_args(argv)
|
|
297
|
+
if args.op == "skills":
|
|
298
|
+
result, code = roster(args.skills_folder, args.forge_data_folder, args.skill)
|
|
299
|
+
else:
|
|
300
|
+
result, code = rows(args.skill, args.versions, args.context_files)
|
|
301
|
+
print(json.dumps(result, indent=2))
|
|
302
|
+
return code
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _force_utf8(*streams) -> None:
|
|
306
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
307
|
+
|
|
308
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
309
|
+
a path may hold.
|
|
310
|
+
"""
|
|
311
|
+
for stream in streams:
|
|
312
|
+
if hasattr(stream, "reconfigure"):
|
|
313
|
+
errors = getattr(stream, "errors", None)
|
|
314
|
+
if errors is None:
|
|
315
|
+
stream.reconfigure(encoding="utf-8")
|
|
316
|
+
else:
|
|
317
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
if __name__ == "__main__":
|
|
321
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
322
|
+
sys.exit(main())
|
|
@@ -1,36 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skf-export-skill
|
|
3
|
-
description:
|
|
3
|
+
description: Exports an SKF skill, validating its package, writing its context snippet and updating the managed section in CLAUDE.md, AGENTS.md or .cursorrules. Use when the user requests to "export a skill" or "package a skill."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Export Skill
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Checks a completed skill against the agentskills.io export gate, writes its context snippet and updates the managed section in CLAUDE.md, AGENTS.md or .cursorrules. It is the sole publishing gate: create-skill and update-skill produce drafts, and only export writes the context files and records the skill in `.export-manifest.json`.
|
|
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/`
|
|
16
|
-
-
|
|
15
|
+
- **Module-level path exception:** bare paths beginning with `knowledge/` or `shared/` resolve from the SKF module root (`{project-root}/_bmad/skf/` installed, `src/` in dev), not the skill root; for example `knowledge/version-paths.md` and `shared/health-check.md`.
|
|
16
|
+
- **Sibling skills:** a path that names another SKF skill's folder (`skf-<name>/...`) resolves from the SKF module root, and that skill must be installed with this one.
|
|
17
|
+
- `references/` holds the workflow stages, chained by frontmatter `nextStepFile`; the files a stage loads when it needs them (`load-skill.md` loads `multi-skill-mode.md` and `preflight-snippet-root-probe.md`, `update-context.md` loads `orphan-context-detection.md`, `orphan-row-detection.md` and `manifest-rebuild.md`); and the headless contract (`invocation-contract.md`, `result-envelope.md`). `assets/` holds the snippet and managed-section templates.
|
|
17
18
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
18
19
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
19
20
|
- `{skill-name}` resolves to the skill directory's basename.
|
|
20
|
-
- **Cross-skill data coupling:**
|
|
21
|
+
- **Cross-skill data coupling:** export-skill, drop-skill and rename-skill write the managed section only through `shared/scripts/skf-rebuild-managed-sections.py`, in the format `assets/managed-section-format.md` documents, and change `.export-manifest.json` only through `shared/scripts/skf-manifest-ops.py` (v2 schema in `references/manifest-rebuild.md`). A change to either contract changes all three skills.
|
|
21
22
|
|
|
22
23
|
## Role
|
|
23
24
|
|
|
24
|
-
You are
|
|
25
|
+
You are Ferris in Delivery mode, a packaging specialist: you check a developer's completed skill against the ecosystem rules and inject it into the context files it targets.
|
|
25
26
|
|
|
26
27
|
## Workflow Rules
|
|
27
28
|
|
|
28
|
-
These rules apply to every step in this workflow:
|
|
29
|
-
|
|
30
29
|
- Only load one step file at a time — never preload future steps
|
|
31
30
|
- Always communicate in `{communication_language}`
|
|
32
31
|
- At any interactive prompt, the inputs `cancel`, `exit`, `[X]`, `q`, or `:q` exit cleanly with exit code 6 (`halt_reason: "user-cancelled"`)
|
|
33
|
-
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and
|
|
32
|
+
- If `{headless_mode}` is true, auto-proceed through confirmation gates with their default action and record each auto-decision in the run sink (`references/result-envelope.md`)
|
|
33
|
+
- Every HALT names its exit code, `halt_reason` and phase; in headless mode it first emits its envelope as `references/result-envelope.md` states
|
|
34
|
+
- **Snippet timing:** step 3 only stages each `context-snippet.md` outside the project; step 4 §9c copies it into the skill package after its [C] gate, as step 4's last write. A cancel, a dry run or a halt before §9c leaves every snippet, and a carried gotchas line's one `[CARRIED]` cycle, as it was.
|
|
34
35
|
|
|
35
36
|
## Stages
|
|
36
37
|
|
|
@@ -46,77 +47,52 @@ These rules apply to every step in this workflow:
|
|
|
46
47
|
|
|
47
48
|
## Invocation Contract
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|--------|--------|
|
|
51
|
-
| **Inputs** | `skill_name` [one or more, required unless `--all`] |
|
|
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"`) |
|
|
53
|
-
| **Gates** | step 1: single Confirm Gate [C] for the whole batch | step 4: single Confirm Gate [C] for the whole batch |
|
|
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` |
|
|
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. |
|
|
56
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Each auto-resolved gate appends a `{gate, default_action, taken_action, reason}` entry to `headless_decisions[]`, surfaced in step 6's `SKF_EXPORT_RESULT_JSON` envelope so non-interactive runs can be audited post-hoc. |
|
|
57
|
-
| **Exit codes** | See "Exit Codes" below |
|
|
58
|
-
|
|
59
|
-
## Exit Codes
|
|
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:
|
|
62
|
-
|
|
63
|
-
| Code | Meaning | Raised by (halt site → `halt_reason`) |
|
|
64
|
-
| ---- | -------------------- | -------------------------------------------------------------------------------------------- |
|
|
65
|
-
| 0 | success | step 7 (terminal); also `status="dry-run"` when `--dry-run` is set |
|
|
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` |
|
|
71
|
-
|
|
72
|
-
## Result Contract (Headless)
|
|
73
|
-
|
|
74
|
-
When `{headless_mode}` is true, step 6 emits a single-line JSON envelope on **stdout** before chaining to step 7, and every HARD HALT emits the same envelope shape on **stderr** with `status: "error"`:
|
|
75
|
-
|
|
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}
|
|
78
|
-
```
|
|
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.
|
|
50
|
+
Headless callers and pipelines: the inputs, flags, gates, outputs and exit codes are in `references/invocation-contract.md`, and the `SKF_EXPORT_RESULT_JSON` envelope in `references/result-envelope.md`. Interactive runs do not need them.
|
|
81
51
|
|
|
82
52
|
## On Activation
|
|
83
53
|
|
|
84
54
|
1. Load config from `{project-root}/_bmad/skf/config.yaml` and resolve:
|
|
85
55
|
- `project_name`, `output_folder`, `user_name`, `communication_language`, `document_output_language`
|
|
86
56
|
- `skills_output_folder`, `forge_data_folder`, `sidecar_path`
|
|
87
|
-
- `snippet_skill_root_override` (optional
|
|
57
|
+
- `snippet_skill_root_override` (optional): the `root:` prefix of every snippet and managed-section row in place of the IDE skill folder, for an authoring repo that keeps its skills in one folder such as `skills/`; step 1's snippet-root option (d) sets it for one run without changing `config.yaml`.
|
|
88
58
|
|
|
89
|
-
2. **
|
|
90
|
-
- `timestamp` ← UTC `YYYYMMDD-HHmmss` captured at activation time. Fixed for the entire workflow run; summary.md reuses this when writing the result contract.
|
|
59
|
+
2. **Resolve `{headless_mode}`**: true if `--headless` or `-H` was passed as an argument, or if `headless_mode: true` in `{sidecar_path}/preferences.yaml`. Default: false.
|
|
91
60
|
|
|
92
|
-
3. **Resolve
|
|
93
|
-
|
|
94
|
-
4. **Resolve workflow customization.** Run:
|
|
61
|
+
3. **Resolve workflow customization.** Run:
|
|
95
62
|
|
|
96
63
|
```bash
|
|
97
|
-
|
|
98
|
-
--skill {skill-root} --key workflow
|
|
64
|
+
uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow
|
|
99
65
|
```
|
|
100
66
|
|
|
101
|
-
|
|
67
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-export-skill.toml` (team overrides, committed) and `.user.toml` (personal overrides, gitignored). When it exits non-zero, prints no JSON or is missing, print one line, `[activation/warn] customization_resolver_unavailable: <reason>` (`<reason>`: its first stderr line, `not found` when the script is missing, `no JSON` when it printed none). If the resolver cannot run, read `{skill-root}/customize.toml` alone and use its bundled defaults: the `{project-root}/_bmad/custom/` overrides do not apply to this run. Keep the reason as `{customization_resolver_unavailable}` (unset when the resolver ran): a headless run records it once step 4 creates its run folder, and an interactive run only prints it.
|
|
102
68
|
|
|
103
|
-
|
|
104
|
-
- `_bmad/custom/<skill-name>.toml` under `{project-root}` — team overrides (committed)
|
|
105
|
-
- `_bmad/custom/<skill-name>.user.toml` under `{project-root}` — personal overrides (gitignored)
|
|
69
|
+
Bind each scalar to its bundled default when the merged value is empty or absent:
|
|
106
70
|
|
|
107
|
-
|
|
71
|
+
- `{snippetFormatPath}` ← `workflow.snippet_format_path`, else `assets/snippet-format.md`
|
|
72
|
+
- `{onCompleteCommand}` ← `workflow.on_complete`, else empty (step 6 then runs no hook)
|
|
108
73
|
|
|
109
|
-
|
|
74
|
+
Then apply the array surfaces: execute each entry in `workflow.activation_steps_prepend` in order now; 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 every `project-context.md` under `{project-root}`; an entry prefixed `!` drops each earlier entry it names and loads nothing itself, so an override's `"!file:{project-root}/**/project-context.md"` turns that default off); then, after activation completes and before the first stage runs, execute each entry in `workflow.activation_steps_append` in order.
|
|
110
75
|
|
|
111
|
-
|
|
112
|
-
- `{snippetFormatPath}` ← `workflow.snippet_format_path` if non-empty, else `assets/snippet-format.md`
|
|
113
|
-
- `{onCompleteCommand}` ← `workflow.on_complete` if non-empty, else empty string (no-op — step 6 skips the hook invocation)
|
|
76
|
+
4. **Resolve the helpers and create the run folder.** Resolve each helper to the first of its two paths that exists, the installed module first, all in parallel:
|
|
114
77
|
|
|
115
|
-
|
|
78
|
+
- `{emitEnvelopeHelper}` ← the first existing path of `{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py` and `{project-root}/src/shared/scripts/skf-emit-result-envelope.py` (every envelope, result file and headless record).
|
|
79
|
+
- `{manifestOpsHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-manifest-ops.py`, else `{project-root}/src/shared/scripts/skf-manifest-ops.py` (steps 1, 3 and 4).
|
|
80
|
+
- `{skillInventoryHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-skill-inventory.py`, else `{project-root}/src/shared/scripts/skf-skill-inventory.py` (step 1).
|
|
81
|
+
- `{rebuildManagedSectionsHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-rebuild-managed-sections.py`, else `{project-root}/src/shared/scripts/skf-rebuild-managed-sections.py` (steps 1 and 4).
|
|
82
|
+
- `{validateOutputHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-validate-output.py`, else `{project-root}/src/shared/scripts/skf-validate-output.py` (steps 1 and 2).
|
|
83
|
+
- `{countTokensHelper}` ← `{project-root}/_bmad/skf/shared/scripts/skf-count-tokens.py`, else `{project-root}/src/shared/scripts/skf-count-tokens.py` (steps 3 and 5).
|
|
116
84
|
|
|
117
|
-
|
|
85
|
+
No step has a fallback for them, so check them all here, before any prompt or write. If one has no existing path, HALT (exit code 4, `halt_reason: "context-rebuild-failed"`) at phase `on-activation §4`: "`{the missing helper's file name}` is missing. Nothing was changed. Re-install SKF."
|
|
118
86
|
|
|
119
|
-
|
|
87
|
+
When `{headless_mode}` is true, create the run folder, whose sink holds the headless auto-decisions:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
mkdir -p "{project-root}/_bmad-output/.skf-run" && mktemp -d "{project-root}/_bmad-output/.skf-run/skf-export-skill-XXXXXXXX"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Bind `{run_dir}` ← the path it prints. Step 6 deletes it once the run's envelope is built; a HARD HALT leaves it in place. If it cannot be created, HALT (exit code 4, `halt_reason: "write-failed"`) at phase `on-activation §4`. When `{customization_resolver_unavailable}` is set, record it there: write `customization_resolver_unavailable: <reason>` to `{run_dir}/resolver-warning.txt` with a file write (a resolver error can hold quotes or `$( )`, so never echo it or type it into an argument), then run `uv run {emitEnvelopeHelper} record --run-dir "{run_dir}" --warning "$(cat "{run_dir}/resolver-warning.txt")"` from `{project-root}`.
|
|
94
|
+
|
|
95
|
+
5. **Pre-flight write check.** Verify `{skills_output_folder}` is writable, so a read-only mount, a full disk or a denied path stops the run before the user confirms a batch. Without `--dry-run`, probe it with a write:
|
|
120
96
|
|
|
121
97
|
```bash
|
|
122
98
|
mkdir -p "{skills_output_folder}" && \
|
|
@@ -124,6 +100,8 @@ SKF_EXPORT_RESULT_JSON: {"status":"success|error|dry-run","skills":[],"context_f
|
|
|
124
100
|
rm "{skills_output_folder}/.skf-write-probe"
|
|
125
101
|
```
|
|
126
102
|
|
|
127
|
-
|
|
103
|
+
With `--dry-run`, check without writing: `test -w "{skills_output_folder}"` when the folder exists; when it does not, skip the check, since step 1 then finds no skill to export.
|
|
104
|
+
|
|
105
|
+
On any non-zero exit: HALT (exit code 4, `halt_reason: "write-failed"`) at phase `on-activation §5`.
|
|
128
106
|
|
|
129
107
|
6. Load, read the full file, and then execute `references/load-skill.md` to begin the workflow.
|
|
@@ -14,9 +14,11 @@
|
|
|
14
14
|
<!-- SKF:END -->
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
`skf-rebuild-managed-sections.py` writes this section for export-skill, drop-skill and rename-skill. Its `assemble` action builds the body between the markers, and its `insert` and `replace` actions write both markers and the `updated:` date around that body, so the three skills write the same bytes for the same manifest and snippets.
|
|
18
|
+
|
|
17
19
|
## IDE → Context File Mapping (config.yaml `ides` list)
|
|
18
20
|
|
|
19
|
-
The installer writes IDE identifiers to `config.yaml` under the `ides` key.
|
|
21
|
+
The installer writes IDE identifiers to `config.yaml` under the `ides` key. The helper's `resolve-targets` action maps each one to its context file and skill root from `shared/data/ide-context-files.json`, the one mapping export-skill, drop-skill and rename-skill read. The tables below show that file for readers, and a test keeps them equal to it.
|
|
20
22
|
|
|
21
23
|
Each IDE has two independent properties:
|
|
22
24
|
|
|
@@ -62,74 +64,36 @@ All other IDEs use AGENTS.md as the context file, each with its own skill direct
|
|
|
62
64
|
|
|
63
65
|
### Resolution rules
|
|
64
66
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
**Missing `ides` key:** If the `ides` key is absent from `config.yaml`, treat it as an empty list. If the resulting set is empty (no recognized entries), fall back to AGENTS.md with `.agents/skills/` as the skill root.
|
|
68
|
-
|
|
69
|
-
**Unknown IDE values:** Warn: "Unknown IDE '{value}' in config.yaml — defaulting to AGENTS.md with `.agents/skills/`"
|
|
67
|
+
`resolve-targets` returns one target per context file, with the warnings and notes to show: IDEs that share a context file take the first configured IDE's skill root, and an absent, empty or unknown IDE resolves to AGENTS.md with `.agents/skills/`. To add an IDE, add it to the shared mapping; no workflow setting changes the mapping.
|
|
70
68
|
|
|
71
69
|
**`snippet_skill_root_override` (optional):** Authoring repos where all skills live under one shared on-disk directory (e.g. `skills/`) that does not match any per-IDE skill root may set `snippet_skill_root_override: skills/` in `config.yaml`. When set:
|
|
72
70
|
|
|
73
71
|
- `export-skill/step 3` §2.7 uses the override as `{skill_root}` for snippet generation instead of the IDE-mapped value
|
|
74
|
-
-
|
|
72
|
+
- export-skill, drop-skill and rename-skill pass it to `assemble` as `--skill-root-override`, which makes it the `root:` prefix of every row it writes: a snippet whose prefix already matches passes through unchanged, and any other prefix (a per-IDE prefix on a snippet exported before the override was adopted, or a legacy `skills/` draft) is rewritten to it, so the section uniformly references the real on-disk location
|
|
75
73
|
|
|
76
74
|
Consuming projects (the common case) omit the field and keep the default IDE-mapping behavior. The override is a narrow escape hatch for repos that author skills into `{skills_output_folder}` and never duplicate them into `.claude/skills/`-style directories.
|
|
77
75
|
|
|
78
76
|
### Consumers
|
|
79
77
|
|
|
80
|
-
|
|
78
|
+
Workflows that resolve context files through `resolve-targets`: `export-skill/step 1` (the target context files), `drop-skill/step 1` (the confirmation's context-file count) and step 2, and `rename-skill/step 2` (the rebuild after a management operation).
|
|
81
79
|
|
|
82
80
|
## Four-Case Logic
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Target file does not exist. Create new file with managed section only.
|
|
87
|
-
|
|
88
|
-
```markdown
|
|
89
|
-
<!-- SKF:BEGIN updated:{date} -->
|
|
90
|
-
{managed section content}
|
|
91
|
-
<!-- SKF:END -->
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Case 2: Append (File Exists, No Section)
|
|
95
|
-
|
|
96
|
-
Target file exists but contains no `<!-- SKF:BEGIN` marker. Append managed section at end of file.
|
|
97
|
-
|
|
98
|
-
1. Read existing file content
|
|
99
|
-
2. Append two blank lines
|
|
100
|
-
3. Append managed section with markers
|
|
101
|
-
4. Write file
|
|
102
|
-
|
|
103
|
-
### Case 3: Regenerate (Existing Section)
|
|
82
|
+
`skf-rebuild-managed-sections.py <context-file> check` reports the case of each context file as `case`, and the case picks the write. Both writes take only the body between the markers, as `assemble` stages it:
|
|
104
83
|
|
|
105
|
-
|
|
84
|
+
- **Create** (no file): `insert`, which creates the file with the section.
|
|
85
|
+
- **Append** (a file with no `<!-- SKF:BEGIN` marker): `insert`, which adds the section at the end of the file.
|
|
86
|
+
- **Regenerate** (a file with a section): `replace`, which swaps the old section for the new one and writes both markers itself.
|
|
87
|
+
- **Malformed** (a `<!-- SKF:BEGIN` with no `<!-- SKF:END -->` closing it): HALT and leave the file untouched. `check` names the line; the user restores the end marker, or deletes the stray begin marker, and re-runs.
|
|
106
88
|
|
|
107
|
-
|
|
108
|
-
2. Find `<!-- SKF:BEGIN` line (preserve everything before it)
|
|
109
|
-
3. Find `<!-- SKF:END -->` line (preserve everything after it)
|
|
110
|
-
4. Replace everything between markers (inclusive) with new managed section
|
|
111
|
-
5. Write file
|
|
112
|
-
|
|
113
|
-
### Case 4: Malformed Markers (Halt)
|
|
114
|
-
|
|
115
|
-
Target file contains `<!-- SKF:BEGIN` but no matching `<!-- SKF:END -->` marker. This indicates a corrupted managed section.
|
|
116
|
-
|
|
117
|
-
1. Halt workflow with error: "Malformed managed section — `<!-- SKF:BEGIN` found but no `<!-- SKF:END -->`. Fix the markers manually before re-running export."
|
|
118
|
-
2. Do not modify the file
|
|
89
|
+
drop-skill and rename-skill rewrite only a section that exists: they leave a missing file, or one with no section, as it is (export-skill adds the section on its next run), and report a malformed file as failed while they rebuild the others.
|
|
119
90
|
|
|
120
91
|
## Regeneration: Full Index Rebuild
|
|
121
92
|
|
|
122
|
-
|
|
123
|
-
|
|
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, 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
|
-
3. Count total skills and stack skills (from filtered set only)
|
|
127
|
-
4. Assemble filtered snippets into managed section
|
|
128
|
-
5. Sort alphabetically by skill name
|
|
129
|
-
6. Update the header line with correct counts
|
|
93
|
+
`skf-rebuild-managed-sections.py assemble` builds the body from the **exported skill set** only: the skills of `{skills_output_folder}/.export-manifest.json` whose active version is not deprecated, plus the skills an export names with `--include`. Its docstring lists the rules it applies (where it reads each snippet, the rows of skills the manifest does not know, the sort and the counts).
|
|
130
94
|
|
|
131
95
|
**Rationale:** create-skill and update-skill also write `context-snippet.md` as a build artifact, but only export-skill is the publishing gate (ADR-K). The `.export-manifest.json` file tracks which skills have passed through export-skill, preventing draft skills from leaking into the agent's passive context.
|
|
132
96
|
|
|
133
97
|
## Safety Rules
|
|
134
98
|
|
|
135
|
-
Only the bytes between `<!-- SKF:BEGIN
|
|
99
|
+
Only the bytes between the `<!-- SKF:BEGIN` and `<!-- SKF:END -->` markers are SKF's to rewrite. Everything above and below them is the user's own file (their CLAUDE.md, AGENTS.md or .cursorrules) and must survive byte-for-byte. Route every write through `skf-rebuild-managed-sections.py`: it writes atomically and reads the file back to check the markers and the bytes outside them, so no separate hand-check is needed. On a write failure, report and stop.
|
|
@@ -6,7 +6,9 @@ The snippet `root:` path must reflect the IDE's actual skill directory — its `
|
|
|
6
6
|
|
|
7
7
|
The root path in context-snippet.md on disk uses the **`skill_root` of the first entry in `target_context_files`** (resolved in step 1 from config.yaml IDE mapping). When assembling managed sections for other context files, step 4 rewrites root paths to match the target context file's skill root.
|
|
8
8
|
|
|
9
|
-
**Authoring-repo override:** If `snippet_skill_root_override` is set in `config.yaml`, it replaces the IDE-mapped value for snippet generation and
|
|
9
|
+
**Authoring-repo override:** If `snippet_skill_root_override` is set in `config.yaml`, or for one run by step 1's snippet-root option (d), it replaces the IDE-mapped value for snippet generation and becomes every row's root in step 4. See the "Resolution rules" section in `managed-section-format.md` for the full semantics.
|
|
10
|
+
|
|
11
|
+
**A house-style copy** of this file (`snippet_format_path` in `customize.toml`) keeps the lines scripts parse: line 1 with its `root:` field, line 2's `|IMPORTANT:` start and skill name, the `|gotchas:` label with its `[CARRIED]` marker, and no bare `|` line. That setting's comment says which script reads each one.
|
|
10
12
|
|
|
11
13
|
## Format Rules
|
|
12
14
|
|