bmad-module-skill-forge 2.1.0 → 3.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 +51 -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 +135 -18
- package/docs/verifying-a-skill.md +83 -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 +2 -2
- 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 +32 -12
- package/src/shared/references/description-guard-protocol.md +14 -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 +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- 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 +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- 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 +3 -2
- package/src/shared/scripts/skf-description-guard.py +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- 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 +1 -1
- package/src/shared/scripts/skf-detect-tools.py +215 -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 +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -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 +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -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 +1 -1
- 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 +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- 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 +128 -13
- 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 +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- 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 +4 -1
- 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 +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- 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 +27 -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/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- 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 +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- 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 +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- 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 +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +17 -6
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- 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 +107 -29
- 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 +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- 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 +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- 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 +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- 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 +79 -146
- 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 +118 -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 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- 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 +217 -254
- 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 +163 -88
- 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 +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- 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 +287 -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 +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -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 +127 -52
- 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 +233 -222
- 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 +250 -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 +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- 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 -645
- 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,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
5
|
"""SKF Validate Rename Name — deterministic new-name gate for skf-rename-skill.
|
|
6
6
|
|
|
@@ -12,9 +12,11 @@ with one correct answer for a given (name, filesystem, manifest) state:
|
|
|
12
12
|
skf-validate-output.py / skf-validate-brief-inputs.py)
|
|
13
13
|
length — 1..64 characters (agentskills.io spec)
|
|
14
14
|
identity — differs from the current name (nothing to rename otherwise)
|
|
15
|
-
collision — does NOT already exist
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
collision — does NOT already exist (a folder, a file or a link, even a
|
|
16
|
+
broken one) as a manifest `exports` key, a top-level entry
|
|
17
|
+
under the skills output folder or the forge data folder, and
|
|
18
|
+
is not `improvement-queue`, SKF's own forge folder (location
|
|
19
|
+
kind `reserved`)
|
|
18
20
|
|
|
19
21
|
Doing this in the prompt is a set-membership computation across three sources
|
|
20
22
|
plus a regex match — deterministic, so it lives here and the prompt keeps only
|
|
@@ -23,14 +25,34 @@ the interactive re-ask / halt decision. Checks run in the select.md §5 order an
|
|
|
23
25
|
halt (`input-invalid`/exit 2 for format/length/identity, `name-collision`/exit 5
|
|
24
26
|
for collision).
|
|
25
27
|
|
|
26
|
-
`interrupted_rename` fires the select.md §5 recovery fingerprint
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
`interrupted_rename` fires the select.md §5 recovery fingerprint. A rename's
|
|
29
|
+
copy creates the new skill folder first, and a new forge folder only from an
|
|
30
|
+
old one. So the new name collides only on disk (not in the manifest, not on a
|
|
31
|
+
reserved name), at least under the skills output folder, while the old skill
|
|
32
|
+
folder still exists, and under the forge data folder only when the old skill
|
|
33
|
+
has a forge folder too — the signature of a rename interrupted between copy
|
|
34
|
+
and delete-old, which the caller surfaces as a named cleanup path instead of a
|
|
35
|
+
dead-end collision. The new skill folder must also hold nothing a copy of the
|
|
36
|
+
old one could not: every entry in it exists in the old folder, of the same
|
|
37
|
+
kind, with only each version's package renamed. A forge-only collision
|
|
38
|
+
(another tool's folder with the new name), or a module's own skill or another
|
|
39
|
+
tool's folder at the new name, is a genuine clash.
|
|
40
|
+
|
|
41
|
+
`interrupted_after_rekey` fires the recovery for a rename that stopped after
|
|
42
|
+
it re-keyed the export manifest, before it deleted the old folders: the new
|
|
43
|
+
name is a manifest key and the old name no longer is, both skill folders are
|
|
44
|
+
plain folders, and the old one holds nothing the new one lacks (every entry
|
|
45
|
+
in it exists in the new folder, of the same kind, with only each version's
|
|
46
|
+
package renamed). The new name is then the skill, and the old folders are
|
|
47
|
+
what that run left. `leftover_folders` lists them for the caller to name:
|
|
48
|
+
the old skill folder and, when that rename moved the forge folder (the old
|
|
49
|
+
and new forge folders are plain folders and the old one holds nothing the
|
|
50
|
+
new one lacks), the old forge folder. It is empty when the fingerprint does
|
|
51
|
+
not fire.
|
|
30
52
|
|
|
31
53
|
Output (stdout, always JSON):
|
|
32
54
|
{status, valid, old_name, new_name, first_failure, checks{...},
|
|
33
|
-
interrupted_rename, regex}
|
|
55
|
+
interrupted_rename, interrupted_after_rekey, leftover_folders, regex}
|
|
34
56
|
|
|
35
57
|
Exit codes:
|
|
36
58
|
0 valid (all checks pass)
|
|
@@ -46,13 +68,19 @@ from __future__ import annotations
|
|
|
46
68
|
|
|
47
69
|
import argparse
|
|
48
70
|
import json
|
|
71
|
+
import os
|
|
49
72
|
import re
|
|
50
73
|
import sys
|
|
51
74
|
from pathlib import Path
|
|
52
75
|
|
|
53
76
|
NAME_REGEX = r"^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"
|
|
77
|
+
# Keep identical to FORGE_GROUP_DIRS in skf-skill-inventory.py
|
|
78
|
+
# (test/test-skf-skill-inventory.py pins the copies).
|
|
79
|
+
FORGE_GROUP_DIRS = frozenset({"_campaign", "improvement-queue"})
|
|
54
80
|
MIN_LEN = 1
|
|
55
81
|
MAX_LEN = 64
|
|
82
|
+
# OS clutter that a copy can gain without the rename writing it.
|
|
83
|
+
CLUTTER = frozenset({".DS_Store", "Thumbs.db", "desktop.ini"})
|
|
56
84
|
|
|
57
85
|
|
|
58
86
|
def load_exports(manifest_path: Path) -> tuple[set[str], str | None]:
|
|
@@ -69,6 +97,107 @@ def load_exports(manifest_path: Path) -> tuple[set[str], str | None]:
|
|
|
69
97
|
return set(exports.keys()) if isinstance(exports, dict) else set(), None
|
|
70
98
|
|
|
71
99
|
|
|
100
|
+
# Keep identical to _is_link_or_junction in skf-skill-inventory.py
|
|
101
|
+
# (test/test-skf-skill-inventory.py pins the copies).
|
|
102
|
+
def _is_link_or_junction(p: Path) -> bool:
|
|
103
|
+
"""True for POSIX symlinks AND Windows junctions/symlinks.
|
|
104
|
+
|
|
105
|
+
`Path.is_symlink()` is False for Windows junctions; os.readlink succeeds
|
|
106
|
+
for both symlinks and junctions (since CPython 3.8 on Windows). A regular
|
|
107
|
+
directory raises OSError on readlink, which is the signal we want to
|
|
108
|
+
refuse replacement. On Windows, any other reparse point (a cloud-sync
|
|
109
|
+
placeholder, a deduplicated file, an app execution alias) raises
|
|
110
|
+
ValueError: it does not redirect to another path, so it is not a link.
|
|
111
|
+
"""
|
|
112
|
+
if p.is_symlink():
|
|
113
|
+
return True
|
|
114
|
+
if not p.exists() and not p.is_symlink():
|
|
115
|
+
return False
|
|
116
|
+
try:
|
|
117
|
+
os.readlink(p)
|
|
118
|
+
return True
|
|
119
|
+
except (OSError, ValueError):
|
|
120
|
+
return False
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _within(new: Path, old: Path, depth: int, old_name: str, new_name: str) -> bool:
|
|
124
|
+
"""True when every entry under `new` also exists under `old`, of the same kind.
|
|
125
|
+
|
|
126
|
+
Depth 0 is the skill folder, depth 1 a version folder: there the renamed
|
|
127
|
+
package `{new_name}` stands for `{old_name}`. OS clutter and `.skf-`
|
|
128
|
+
names (the rename's own staging and link swaps) are ignored. A link or
|
|
129
|
+
junction matches only a link or junction.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
children = list(new.iterdir())
|
|
133
|
+
except OSError:
|
|
134
|
+
return False
|
|
135
|
+
for child in children:
|
|
136
|
+
name = child.name
|
|
137
|
+
if name in CLUTTER or ".skf-" in name:
|
|
138
|
+
continue
|
|
139
|
+
counterpart = old / (old_name if depth == 1 and name == new_name else name)
|
|
140
|
+
child_link, counterpart_link = _is_link_or_junction(child), _is_link_or_junction(counterpart)
|
|
141
|
+
if child_link or counterpart_link:
|
|
142
|
+
if not (child_link and counterpart_link):
|
|
143
|
+
return False
|
|
144
|
+
elif child.is_dir():
|
|
145
|
+
if not counterpart.is_dir() or not _within(child, counterpart, depth + 1, old_name, new_name):
|
|
146
|
+
return False
|
|
147
|
+
elif not counterpart.is_file():
|
|
148
|
+
return False
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _looks_like_a_copy(skills_dir: Path, old_name: str, new_name: str) -> bool:
|
|
153
|
+
"""True when `{skills_dir}/{new_name}` could be a rename's copy of `{old_name}`.
|
|
154
|
+
|
|
155
|
+
The copy takes the old skill folder as it is, then renames each version's
|
|
156
|
+
`{old_name}/` package to `{new_name}/` and rewrites files in place, so it
|
|
157
|
+
never holds an entry the old folder lacks. A module's own skill (a root
|
|
158
|
+
`SKILL.md`) or another tool's folder with the new name does.
|
|
159
|
+
"""
|
|
160
|
+
new, old = skills_dir / new_name, skills_dir / old_name
|
|
161
|
+
if (_is_link_or_junction(new) or not new.is_dir()
|
|
162
|
+
or _is_link_or_junction(old) or not old.is_dir()):
|
|
163
|
+
return False
|
|
164
|
+
return _within(new, old, 0, old_name, new_name)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _copied_whole(old: Path, new: Path, old_name: str, new_name: str) -> bool:
|
|
168
|
+
"""True when both are plain folders and `old` holds nothing `new` lacks.
|
|
169
|
+
|
|
170
|
+
_within with the two folders swapped: every entry under the old folder
|
|
171
|
+
exists under the new one, of the same kind, where a version's
|
|
172
|
+
`{old_name}/` package stands for its renamed `{new_name}/`.
|
|
173
|
+
"""
|
|
174
|
+
if (_is_link_or_junction(old) or not old.is_dir()
|
|
175
|
+
or _is_link_or_junction(new) or not new.is_dir()):
|
|
176
|
+
return False
|
|
177
|
+
return _within(old, new, 0, new_name, old_name)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _rekeyed_leftovers(skills_dir: Path, forge_dir: Path, old_name: str, new_name: str,
|
|
181
|
+
export_keys: set[str]) -> list[str]:
|
|
182
|
+
"""The old folders a rename left when it stopped after re-keying the manifest.
|
|
183
|
+
|
|
184
|
+
Empty unless the manifest names the new skill and no longer the old one,
|
|
185
|
+
and the old skill folder holds nothing the new one lacks. The old forge
|
|
186
|
+
folder is listed only when the rename moved it: its copy under the new
|
|
187
|
+
name exists, and it holds nothing that copy lacks. When both settings
|
|
188
|
+
name one folder, the skill folder is the forge folder, listed once.
|
|
189
|
+
"""
|
|
190
|
+
if new_name not in export_keys or old_name in export_keys:
|
|
191
|
+
return []
|
|
192
|
+
if not _copied_whole(skills_dir / old_name, skills_dir / new_name, old_name, new_name):
|
|
193
|
+
return []
|
|
194
|
+
leftovers = [str(skills_dir / old_name)]
|
|
195
|
+
if (os.path.realpath(forge_dir) != os.path.realpath(skills_dir)
|
|
196
|
+
and _copied_whole(forge_dir / old_name, forge_dir / new_name, old_name, new_name)):
|
|
197
|
+
leftovers.append(str(forge_dir / old_name))
|
|
198
|
+
return leftovers
|
|
199
|
+
|
|
200
|
+
|
|
72
201
|
def validate_name(
|
|
73
202
|
old_name: str,
|
|
74
203
|
new_name: str,
|
|
@@ -90,24 +219,33 @@ def validate_name(
|
|
|
90
219
|
locations: list[dict] = []
|
|
91
220
|
if new_name in export_keys:
|
|
92
221
|
locations.append({"kind": "manifest.exports", "path": str(manifest)})
|
|
93
|
-
if (skills_dir / new_name)
|
|
222
|
+
if os.path.lexists(skills_dir / new_name):
|
|
94
223
|
locations.append({"kind": "skills_output_folder", "path": str(skills_dir / new_name)})
|
|
95
|
-
if (forge_dir / new_name)
|
|
224
|
+
if os.path.lexists(forge_dir / new_name):
|
|
96
225
|
locations.append({"kind": "forge_data_folder", "path": str(forge_dir / new_name)})
|
|
226
|
+
if new_name in FORGE_GROUP_DIRS:
|
|
227
|
+
locations.append({"kind": "reserved", "path": str(forge_dir / new_name)})
|
|
97
228
|
collision_ok = len(locations) == 0
|
|
98
229
|
|
|
99
|
-
# Recovery fingerprint:
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
230
|
+
# Recovery fingerprint: a rename's copy creates the new skill folder
|
|
231
|
+
# first, and a new forge folder only from an old one. So a stranded
|
|
232
|
+
# partial rename collides on disk only (not in the manifest, not on a
|
|
233
|
+
# reserved name), at least in the skills folder, where the new folder
|
|
234
|
+
# holds nothing a copy of the old skill folder could not, and at the
|
|
235
|
+
# forge folder only when the old skill has one.
|
|
236
|
+
kinds = {loc["kind"] for loc in locations}
|
|
106
237
|
interrupted_rename = bool(
|
|
107
|
-
|
|
108
|
-
and
|
|
109
|
-
and
|
|
238
|
+
not collision_ok
|
|
239
|
+
and not kinds & {"manifest.exports", "reserved"}
|
|
240
|
+
and "skills_output_folder" in kinds
|
|
241
|
+
and _looks_like_a_copy(skills_dir, old_name, new_name)
|
|
242
|
+
and ("forge_data_folder" not in kinds or (forge_dir / old_name).is_dir())
|
|
110
243
|
)
|
|
244
|
+
# Recovery fingerprint after the commit point: the manifest already names
|
|
245
|
+
# the new skill, so the new name is taken for good, and the old folders are
|
|
246
|
+
# what the rename left before it deleted them.
|
|
247
|
+
leftover_folders = [] if collision_ok or "reserved" in kinds else _rekeyed_leftovers(
|
|
248
|
+
skills_dir, forge_dir, old_name, new_name, export_keys)
|
|
111
249
|
|
|
112
250
|
checks = {
|
|
113
251
|
"format": {"ok": fmt_ok},
|
|
@@ -130,6 +268,8 @@ def validate_name(
|
|
|
130
268
|
"first_failure": first_failure,
|
|
131
269
|
"checks": checks,
|
|
132
270
|
"interrupted_rename": interrupted_rename,
|
|
271
|
+
"interrupted_after_rekey": bool(leftover_folders),
|
|
272
|
+
"leftover_folders": leftover_folders,
|
|
133
273
|
"regex": NAME_REGEX,
|
|
134
274
|
}
|
|
135
275
|
if manifest_error:
|
package/src/skf-setup/SKILL.md
CHANGED
|
@@ -1,32 +1,37 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skf-setup
|
|
3
|
-
description: Initialize forge environment, detect tools, and set capability tier (Quick/Forge/Forge+/Deep). Use when the user requests to "set up" or "initialize the forge".
|
|
3
|
+
description: Initialize forge environment, detect tools, and set capability tier (Quick/Forge/Forge+/Deep). Use when the user requests to "set up the forge" or "initialize the forge".
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Setup Forge
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
Detects the available tools, sets the capability tier (Quick/Forge/Forge+/Deep) and writes the forge configuration to `{sidecar_path}/`. With `ccc` it also keeps `.cocoindex_code/settings.yml` and the semantic-search index current, and it reconciles the QMD and CCC index registries.
|
|
11
11
|
|
|
12
12
|
## Conventions
|
|
13
13
|
|
|
14
14
|
- Bare paths (e.g. `references/<name>.md`) resolve from the skill root.
|
|
15
|
-
- `
|
|
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.
|
|
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 invocation contract and `tier-rules.md`, the tier table `report.md` passes to the emitter's banner.
|
|
18
|
+
- Shared Python helpers do the deterministic work, with no prose fallback: each step's frontmatter `*ProbeOrder` array lists a helper's installed path (`{project-root}/_bmad/skf/shared/scripts/<name>`) then its dev path (`{project-root}/src/shared/scripts/<name>`). Run the first that exists; if none does, halt through the On Activation halt contract with phase `step <N>:helper-missing`.
|
|
17
19
|
- `{skill-root}` resolves to this skill's installed directory (where `customize.toml` lives, if present).
|
|
18
20
|
- `{project-root}`-prefixed paths resolve from the project working directory.
|
|
19
21
|
- `{skill-name}` resolves to the skill directory's basename.
|
|
20
22
|
|
|
21
23
|
## Role
|
|
22
24
|
|
|
23
|
-
You are a system executor
|
|
25
|
+
You are a system executor: run each step in sequence, write the configuration files, and report at completion.
|
|
24
26
|
|
|
25
27
|
## Workflow Rules
|
|
26
28
|
|
|
27
29
|
- Only load one step file at a time — never preload future steps.
|
|
28
30
|
- Communicate in `{communication_language}`.
|
|
29
|
-
- If `{
|
|
31
|
+
- If `{orphan_action}` is non-null or `{quiet_mode}` is true, resolve the step 3 orphan-removal gate without prompting; step 3 records the decision for the envelope's `warnings`.
|
|
32
|
+
- When `{quiet_mode}` is true, the one line setup displays is the `SKF_SETUP_RESULT_JSON: {…}` envelope that step 4 builds, or the blocked envelope from a halt. In a standalone run that line is the run's final message, so it is all `claude -p` prints (step 4 holds it until then).
|
|
33
|
+
- When `{quiet_mode}` is true, write no assistant text at all between tool calls: no status, progress or step-transition notes, however brief.
|
|
34
|
+
- When `{pipeline_mode}` is true, the forger runs setup as one step of a pipeline: display the same envelope line, then return control to the forger, which keeps chaining. The final-message rule above is for a standalone run.
|
|
30
35
|
|
|
31
36
|
## Stages
|
|
32
37
|
|
|
@@ -41,38 +46,33 @@ You are a system executor performing environment resolution. Run each step in se
|
|
|
41
46
|
|
|
42
47
|
## Invocation Contract
|
|
43
48
|
|
|
44
|
-
|
|
45
|
-
|--------|--------|
|
|
46
|
-
| **Inputs** | (none) |
|
|
47
|
-
| **Flags** | `--headless` / `-H` (skip prompts, auto-resolve gates to defaults); `--require-tier=<Quick\|Forge\|Forge+\|Deep>` (halt with failure if calculated tier does not satisfy the requirement); `--orphan-action=<keep\|remove>` (resolve the orphan-removal gate non-interactively, even outside `--headless`); `--ccc-skip-index` (skip CCC indexing; envelope `ccc_index.status` becomes `"skipped"` — the fast re-probe lane for an expert re-running only to refresh the detected tier without paying the full ccc re-index cost); `--quiet` (suppress the human-readable FORGE STATUS banner — pipelines and expert re-runners get the envelope only) |
|
|
48
|
-
| **Gates** | One optional: orphaned QMD collection removal (step 3, Deep tier only; default: Keep, or whatever `--orphan-action` set) |
|
|
49
|
-
| **Outputs** | `forger-sidecar/forge-tier.yaml`, `forger-sidecar/preferences.yaml`, `{forge_data_folder}/`; when ccc is available, `.cocoindex_code/settings.yml` (exclusion patterns merged) and the project ccc index |
|
|
50
|
-
| **Headless** | All gates auto-resolve with default action when `{headless_mode}` is true. Under `--headless` or `--quiet`, step 4 emits a single-line `SKF_SETUP_RESULT_JSON: {…}` envelope as the only stdout line — the FORGE STATUS banner is suppressed. Branch on the envelope's top-level `status` field: `success`, `tier_failure` (require-tier miss), or `blocked` (any pre-report halt — the `error.phase` names the cause). `skf-emit-result-envelope.py` derives `status` from the payload, so pipelines never compose it from `require_tier_satisfied` + `error`. Schema in `references/report.md` §4. |
|
|
51
|
-
| **Failure modes** | `--require-tier` not satisfied → status `tier_failure`, the envelope sets `"require_tier_satisfied": false`, and the workflow halts before step 5 (interactive runs also print a "REQUIRED TIER NOT MET" block). A write failure (forge-tier.yaml or preferences.yaml could not be written) halts step 2 with a `blocked` envelope whose `error.phase` (`step 2:write-tools` or `step 2:init-prefs`) and `error` name the path and reason. |
|
|
49
|
+
`references/invocation-contract.md` holds the flags, gate, outputs, `SKF_SETUP_RESULT_JSON` contract and failure modes for pipeline authors; no run loads it.
|
|
52
50
|
|
|
53
51
|
## On Activation
|
|
54
52
|
|
|
55
|
-
> **Halt contract
|
|
53
|
+
> **Halt contract.** Every halt in this workflow that names a phase, below and in the step files, follows this contract; the step 4 tier-miss halt names none and displays its `tier_failure` envelope instead. When `{quiet_mode}` is true, run `<runner> <helper> emit-blocked --phase '<phase>' --reason '<reason>' --path "<path>"` (`<path>` written with `/`; no `--path` for a halt without one): `<helper>` is `skf-emit-result-envelope.py`, at its installed path, else its dev path (Conventions), and `<runner>` is `uv run` in the step files. The halts in this list can fire before `uv` is proven present, so for them try the runners `uv run`, `python3`, `python` and `py -3` in that order and stop at the first that exits 0 and prints a line starting `SKF_SETUP_RESULT_JSON:` (the helper is stdlib-only). Display that line verbatim, nothing else. If neither path exists, or no runner exits 0 and prints that line, display the reason alone as that one line. Interactive runs display the reason as their diagnostic and emit no envelope. Text from the user or a helper never goes into the single-quoted `--reason`: write `<message>` there and pass the text on stdin with `--stderr-from -` and a quoted `<<'SKF_TEXT'` heredoc, one per runner tried, for the emitter to fold to one line and escape. Once item 6 binds `{customization_resolver_unavailable}` to a reason, every halt also passes `--customization-resolver-unavailable "<reason>"`, escaping each `\`, `"`, `$` and backtick in it with a backslash. `--headless` and `--quiet` are known at every halt, `headless_mode` from `preferences.yaml` only after item 5.
|
|
56
54
|
|
|
57
|
-
1. **Parse invocation flags first
|
|
55
|
+
1. **Parse invocation flags first**, so every halt below knows whether to emit: `{headless_mode}` (true on `--headless` / `-H`, and when `{pipeline_mode}` is true), `{quiet_mode}` (true on `--quiet`, the alias of `--headless`, and whenever `{headless_mode}` is true), `{require_tier}` and `{orphan_action}` (each flag's raw value exactly as given, after `=` or a space; an empty string when nothing, or another flag, follows it; null only when the flag is absent), `{ccc_skip_index}` (true on `--ccc-skip-index`, false otherwise). `{pipeline_mode}` is not a flag: it is true only when the forger invokes setup as one step of a pipeline and passes it, and false otherwise, including every direct `/skf-setup` run. Step 1's detector rejects a `{require_tier}` that names no tier. When `{orphan_action}` is non-null and neither `keep` nor `remove`, halt with phase `on-activation:orphan-action-invalid`, no `path`, and reason `Setup cannot proceed: --orphan-action takes keep or remove, not <message>. Re-run with --orphan-action=keep or --orphan-action=remove.`, where `<message>` is the value as given, or `an empty value` when there is none, passed as the halt contract says.
|
|
58
56
|
|
|
59
|
-
2. **
|
|
57
|
+
2. **Check for the config.** If `{project-root}/_bmad/skf/config.yaml` does not exist, halt with phase `on-activation:config-missing`, `path` `{project-root}/_bmad/skf/config.yaml`, and reason `Setup cannot proceed: the SKF config file was not found. SKF is not installed in this project: from the project root, run npx bmad-module-skill-forge install (or npx bmad-method install and add SKF), then re-run /skf-setup.`
|
|
60
58
|
|
|
61
|
-
|
|
59
|
+
3. **Probe `uv` runtime.** Run `uv --version`. If `uv` is missing, halt with phase `on-activation:uv-missing`, no `path`, and reason `Setup cannot proceed: uv is not installed. Install it from https://docs.astral.sh/uv/getting-started/installation/ and re-run /skf-setup.`
|
|
62
60
|
|
|
63
|
-
|
|
61
|
+
4. **Load config and preferences.** `<preflight>` is `skf-preflight.py`, at its installed path, else its dev path (neither: halt with phase `on-activation:helper-missing`, `path` the installed one, reason `Setup cannot proceed: skf-preflight.py was not found. Reinstall SKF, then re-run /skf-setup.`). Run `uv run "<preflight>" "{project-root}" --allow-missing-sidecar`, which parses config.yaml and `preferences.yaml` into one JSON object:
|
|
64
62
|
|
|
65
|
-
|
|
63
|
+
- `status` `ok`: bind `{project_name}`, `{user_name}`, `{communication_language}` and `{document_output_language}` from `config`, each folder from its absolute path (`{output_folder}`, `{skills_output_folder}`, `{forge_data_folder}` and `{sidecar_path}` from `config.<name>_resolved`), and `{tier_override}` from `sidecar.preferences.tier_override` (null when absent). With `sidecar.preferences_error`, go on with the defaults and, unless `{quiet_mode}` is true, display that error in one line.
|
|
64
|
+
- `code` `CONFIG_MISSING`: the item 2 halt. Any other `hard-halt`, or no JSON `status`: halt with phase `on-activation:config-malformed`, `path` `{project-root}/_bmad/skf/config.yaml`, and reason `Setup cannot proceed: <message>`: its `error` without the leading `Cannot initialize. ` (else the first line the call printed), passed as the halt contract says, never preflight's whole JSON.
|
|
66
65
|
|
|
67
|
-
5. **
|
|
66
|
+
5. **Reconcile `{headless_mode}`**: OR it with `derived.headless_mode` (`headless_mode: true` in `preferences.yaml`). Then, if `{headless_mode}` is true, set `{quiet_mode}` to true.
|
|
67
|
+
|
|
68
|
+
6. **Resolve workflow customization.** Run:
|
|
68
69
|
|
|
69
70
|
```bash
|
|
70
|
-
|
|
71
|
-
--skill {skill-root} --key workflow
|
|
71
|
+
uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-root} --key workflow
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
It merges the bundled `{skill-root}/customize.toml` with `{project-root}/_bmad/custom/skf-setup.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. Bind `{customization_resolver_unavailable}` ← that reason (null when the resolver ran), which `references/report.md` and every later halt hand to the emitter; print the warning only when `{quiet_mode}` is false.
|
|
75
75
|
|
|
76
|
-
Apply the resolved values
|
|
76
|
+
Apply the resolved values, with no narration of your own when `{quiet_mode}` is true: run each `workflow.activation_steps_prepend` entry now; treat each `workflow.persistent_facts` entry as standing context (a `file:` entry loads its file or glob contents); bind `{onCompleteCommand}` ← `workflow.on_complete` (empty: no hook) for `references/report.md` §5; then run each `workflow.activation_steps_append` entry, before the first stage.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
7. Execute `references/detect-and-tier.md`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# DO NOT EDIT -- overwritten on every update.
|
|
2
2
|
#
|
|
3
3
|
# Workflow customization surface for skf-setup.
|
|
4
|
-
# Team overrides: _bmad/custom/skf-setup.toml
|
|
5
|
-
# Personal overrides: _bmad/custom/skf-setup.user.toml
|
|
4
|
+
# Team overrides: {project-root}/_bmad/custom/skf-setup.toml
|
|
5
|
+
# Personal overrides: {project-root}/_bmad/custom/skf-setup.user.toml
|
|
6
6
|
|
|
7
7
|
[workflow]
|
|
8
8
|
|
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
# scalars: override wins • arrays (persistent_facts, activation_steps_*): append
|
|
11
11
|
# arrays-of-tables with `code`/`id`: replace matching items, append new ones.
|
|
12
12
|
|
|
13
|
-
# Steps
|
|
14
|
-
#
|
|
15
|
-
#
|
|
13
|
+
# Steps run at activation after flag parsing, the config check, the uv
|
|
14
|
+
# probe and the config load (SKILL.md On Activation items 1-5), and
|
|
15
|
+
# before tool detection (step 1). Overrides append. Use for org-wide
|
|
16
|
+
# pre-flight checks (auth, network, compliance) that must precede any
|
|
17
|
+
# forge setup work. A prepend step cannot install uv or create
|
|
18
|
+
# config.yaml: those halts fire before it runs.
|
|
16
19
|
|
|
17
20
|
activation_steps_prepend = []
|
|
18
21
|
|
|
@@ -34,10 +37,11 @@ activation_steps_append = []
|
|
|
34
37
|
|
|
35
38
|
persistent_facts = []
|
|
36
39
|
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
40
|
+
# An instruction the agent carries out (not a shell command run with
|
|
41
|
+
# --result-path, as in most other SKF workflows) once the forge is fully
|
|
42
|
+
# configured: after the status report (step 4), before chaining to the
|
|
43
|
+
# shared health check, and never on a required-tier miss. Override wins. Use
|
|
44
|
+
# for a post-setup action an org wants, such as notifying an onboarding
|
|
45
|
+
# channel. Empty string = no terminal hook.
|
|
42
46
|
|
|
43
47
|
on_complete = ""
|
|
@@ -2,125 +2,112 @@
|
|
|
2
2
|
nextStepFile: 'report.md'
|
|
3
3
|
# `{qmdClassifyHelper}` and `{forgeTierRwHelper}` = first existing path in
|
|
4
4
|
# their `*ProbeOrder` arrays; halt if neither exists for a helper a section
|
|
5
|
-
# actually invokes.
|
|
6
|
-
# contracts with no prose fallback.
|
|
5
|
+
# actually invokes.
|
|
7
6
|
qmdClassifyProbeOrder:
|
|
8
7
|
- '{project-root}/_bmad/skf/shared/scripts/skf-qmd-classify-collections.py'
|
|
9
8
|
- '{project-root}/src/shared/scripts/skf-qmd-classify-collections.py'
|
|
10
9
|
forgeTierRwProbeOrder:
|
|
11
10
|
- '{project-root}/_bmad/skf/shared/scripts/skf-forge-tier-rw.py'
|
|
12
11
|
- '{project-root}/src/shared/scripts/skf-forge-tier-rw.py'
|
|
12
|
+
# `{emitEnvelopeHelper}` = first existing path in `{emitEnvelopeProbeOrder}`,
|
|
13
|
+
# for the blocked envelope a halt in this step emits under headless or quiet.
|
|
14
|
+
emitEnvelopeProbeOrder:
|
|
15
|
+
- '{project-root}/_bmad/skf/shared/scripts/skf-emit-result-envelope.py'
|
|
16
|
+
- '{project-root}/src/shared/scripts/skf-emit-result-envelope.py'
|
|
13
17
|
---
|
|
14
18
|
|
|
15
|
-
<!-- Config: communicate in {communication_language}. The orphan-removal prompt and the
|
|
19
|
+
<!-- Config: communicate in {communication_language}. The orphan-removal prompt and the `--orphan-action` auto-decision message (interactive runs only) render in the user's language. -->
|
|
16
20
|
|
|
17
21
|
# Step 3: QMD + CCC Registry Hygiene
|
|
18
22
|
|
|
19
23
|
## STEP GOAL:
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
For Quick and Forge tiers, skip silently and proceed (QMD is not available; ccc registry cleanup only runs when ccc is available regardless of tier).
|
|
25
|
+
Reconcile the `qmd_collections` registry with the live QMD collections, and prune `ccc_index_registry` entries whose source paths no longer exist. Classification, removal and every registry edit go through `{qmdClassifyHelper}` and `{forgeTierRwHelper}`, which stage their results in the run folder for step 4; this step runs them and makes the orphan-removal decision.
|
|
24
26
|
|
|
25
27
|
## Rules
|
|
26
28
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
29
|
+
- Never reimplement the forge-namespace suffix filter or the path check in prose: the classifier owns them
|
|
30
|
+
- Run `qmd collection remove`, through the classifier's `remove-orphans`, only on the interactive **[R]** or an explicit `--orphan-action=remove`: that flag is the consent, and `{orphan_auto_resolution}` records the decision, so the envelope's `warnings` name each collection the removal deleted
|
|
31
|
+
- Display messages only when `{quiet_mode}` is false; the one exception is the envelope line a halt displays
|
|
32
|
+
- When `{quiet_mode}` is true, write no assistant text at all between tool calls: no status, progress or step-transition notes, however brief
|
|
33
|
+
- If a section needs a helper whose probe order has no existing path, halt (an install fault, not a hygiene error) with phase `step 3:helper-missing`, `path` set to that array's first entry, and reason `Setup cannot proceed: <script file name> was not found. Reinstall SKF, then re-run /skf-setup.`
|
|
34
|
+
- Every halt follows the SKILL.md halt contract: when `{quiet_mode}` is true, run `uv run {emitEnvelopeHelper} emit-blocked --phase '<phase>' --reason '<reason>' --path "<path>"`, which builds the payload itself (no `--path` for a halt without one, `--stderr-from` where the halt names it, and `--customization-resolver-unavailable "<reason>"` once SKILL.md On Activation item 6 bound `{customization_resolver_unavailable}` to a reason, escaped as the halt contract says), and display its stdout line verbatim and nothing else (the reason alone if `{emitEnvelopeHelper}` resolves to no path, or the helper exits non-zero or prints no line); otherwise display the reason
|
|
35
|
+
- Do not fail the workflow if hygiene encounters errors; a missing helper is not a hygiene error
|
|
32
36
|
|
|
33
37
|
## MANDATORY SEQUENCE
|
|
34
38
|
|
|
35
39
|
### 1. Check Tier
|
|
36
40
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
Default `{orphan_auto_resolution}` to null at the top of this step; only the non-interactive branch in section 3 overrides it.
|
|
40
|
-
|
|
41
|
-
**If `{calculated_tier}` is Quick or Forge AND `{ccc}` is false:** No registry hygiene needed. Set `{hygiene_result: "skipped", hygiene_healthy: 0, hygiene_orphaned_removed: 0, hygiene_orphaned_kept: 0, hygiene_stale_cleaned: 0, ccc_registry_stale_cleaned: 0, ccc_registry_stale_removed_paths: []}`. Proceed directly to section 5 (Auto-Proceed) — no output, no messaging.
|
|
42
|
-
|
|
43
|
-
**If `{calculated_tier}` is Quick or Forge AND `{ccc}` is true:** No QMD work, but ccc registry needs pruning. Set QMD-related flags to defaults (`hygiene_result: "skipped"`, all hygiene_* counts = 0). Skip directly to section 4 (Stale Registry Cleanup), running it with the ccc-prune flag only.
|
|
44
|
-
|
|
45
|
-
**If `{calculated_tier}` is Forge+:** Same as Quick/Forge with ccc — no QMD work (qmd unavailable at Forge+), but ccc registry hygiene runs.
|
|
46
|
-
|
|
47
|
-
**If `{calculated_tier}` is Deep:** Continue to section 2.
|
|
41
|
+
Set `{orphan_auto_resolution}` to null. When `{require_tier_satisfied}` (from step 1) is `false`, go to section 5 with no output: a run that ends `tier_failure` runs no hygiene, so it removes no collection and prunes no registry entry, and step 4 reports none. Otherwise the QMD sections (2 and 3) run only when `{calculated_tier}` is Deep, and section 4 runs when `{calculated_tier}` is Deep or `{ccc}` is true. Go to section 2 at Deep tier, else to section 4 when `{ccc}` is true, else to section 5 with no output.
|
|
48
42
|
|
|
49
43
|
### 2. Classify Live QMD Collections vs Registry
|
|
50
44
|
|
|
51
|
-
Run the classifier — it owns the `qmd collection list` invocation and stdout parsing. Invoke via `uv run`:
|
|
52
|
-
|
|
53
45
|
```bash
|
|
54
|
-
uv run {qmdClassifyHelper} \
|
|
55
|
-
--registry-from-yaml "{
|
|
46
|
+
uv run {qmdClassifyHelper} classify \
|
|
47
|
+
--registry-from-yaml "{sidecar_path}/forge-tier.yaml" \
|
|
48
|
+
--project-root "{project-root}" \
|
|
49
|
+
> "{run_dir}/qmd-classify.json" && cat "{run_dir}/qmd-classify.json"
|
|
56
50
|
```
|
|
57
51
|
|
|
58
|
-
|
|
52
|
+
**If the script exits non-zero** (qmd is not running), go to section 4: step 4 reports QMD hygiene as skipped.
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
Otherwise bind `{orphaned_collections}` ← `orphaned` and `{orphan_paths}` ← `orphaned_paths`.
|
|
61
55
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- `{hygiene_healthy}` ← `len(healthy)`
|
|
65
|
-
- `{orphaned_collections}` ← `orphaned` (the list — used in section 3)
|
|
66
|
-
- `{stale_collections}` ← `stale` (the list — used in section 4)
|
|
67
|
-
- `{live_collections}` ← comma-join of `live_names` (used in §4's clean-stale invocation; the script owns the raw set, the prompt only forwards it)
|
|
68
|
-
- `{foreign_filtered_count}` ← `foreign_filtered_count`
|
|
56
|
+
### 3. Handle Orphaned Collections
|
|
69
57
|
|
|
70
|
-
|
|
58
|
+
**If `{orphaned_collections}` is empty:** go to section 4.
|
|
71
59
|
|
|
72
|
-
|
|
60
|
+
**Non-interactive resolution.** Resolve the gate without prompting in the first case that applies:
|
|
73
61
|
|
|
74
|
-
|
|
62
|
+
- `{orphan_action}` is non-null (activation lets through only `keep` or `remove`) → act on that value, with `source: "orphan-action-flag"`
|
|
63
|
+
- `{quiet_mode}` is true → the default **Keep**, with `source: "headless-default"` when `{headless_mode}` is true and `source: "quiet-default"` otherwise (a `--quiet` run)
|
|
75
64
|
|
|
76
|
-
|
|
65
|
+
Set `{orphan_auto_resolution: {action: <keep|remove>, source: <as above>}}`: step 4 adds the count and, after a removal, the collections removed and not removed, so the envelope's `warnings` audit a decision nobody confirmed. On keep, unless `{quiet_mode}` is true, display `"Auto-decision (--orphan-action=keep): kept {count} orphaned forge collection(s)"`, where `{count}` is the number of names in `{orphaned_collections}`, then go to section 4. On remove, run the removal below, still with no prompt.
|
|
77
66
|
|
|
78
|
-
**
|
|
67
|
+
**GATE [default: K]**: reached only when none of the cases above applies, so `{orphan_action}` is null and `{quiet_mode}` is false. Display to the user:
|
|
79
68
|
|
|
80
|
-
"**QMD Hygiene: Found {count} orphaned collection(s)
|
|
69
|
+
"**QMD Hygiene: Found {count} orphaned collection(s) in this project that its forge registry does not list:**
|
|
81
70
|
|
|
82
|
-
{
|
|
71
|
+
{each name in `{orphaned_collections}`, with its path from `{orphan_paths}`}
|
|
83
72
|
|
|
84
|
-
|
|
73
|
+
QMD indexes them from folders inside this project, but no skill workflow recorded them: a run may have stopped before it registered one, or one was added by hand.
|
|
85
74
|
|
|
86
75
|
**[R]emove** orphaned collections — clean up QMD
|
|
87
76
|
**[K]eep** orphaned collections — leave them as-is (default)"
|
|
88
77
|
|
|
89
|
-
|
|
78
|
+
On the user's **[K]**, go to section 4.
|
|
79
|
+
|
|
80
|
+
**On remove** (the user's **[R]**, or the non-interactive remove):
|
|
90
81
|
|
|
91
82
|
```bash
|
|
92
|
-
|
|
83
|
+
uv run {qmdClassifyHelper} remove-orphans \
|
|
84
|
+
--classification-from "{run_dir}/qmd-classify.json" \
|
|
85
|
+
--project-root "{project-root}" \
|
|
86
|
+
> "{run_dir}/qmd-remove.json" && cat "{run_dir}/qmd-remove.json"
|
|
93
87
|
```
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
**If user selects K (Keep) or no orphans:** Set `{hygiene_orphaned_removed: 0, hygiene_orphaned_kept: len(orphaned_collections)}`.
|
|
89
|
+
It reads each orphan's path again just before removing it, removes only a collection that still lies inside this project, and lists the names under `removed` and `failed` (each failure's reason under `errors`). After a non-interactive remove, unless `{quiet_mode}` is true, display `"Auto-decision (--orphan-action=remove): removed {removed, comma-separated}"`, followed by `"; could not remove: {failed, comma-separated}"` when `failed` is non-empty.
|
|
98
90
|
|
|
99
91
|
### 4. Stale Registry Cleanup
|
|
100
92
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
Build the invocation. Always include `--target` for the forge-tier.yaml path. Include `--qmd-live-names "{live_collections}"` ONLY when section 2 ran successfully (i.e. `{hygiene_result}` is `"completed"`); omit the flag entirely otherwise so the script skips QMD cleanup. Include `--prune-missing-ccc-paths` ONLY when `{ccc}` is true; omit it otherwise.
|
|
93
|
+
Pass `--prune-missing-ccc-paths` when `{ccc}` is true, and leave it out otherwise:
|
|
104
94
|
|
|
105
95
|
```bash
|
|
106
96
|
uv run {forgeTierRwHelper} clean-stale \
|
|
107
|
-
--target "{
|
|
108
|
-
|
|
109
|
-
[--prune-missing-ccc-paths]
|
|
97
|
+
--target "{sidecar_path}/forge-tier.yaml" \
|
|
98
|
+
--qmd-live-from "{run_dir}/qmd-classify.json" \
|
|
99
|
+
[--prune-missing-ccc-paths] \
|
|
100
|
+
> "{run_dir}/clean-stale.json" && cat "{run_dir}/clean-stale.json"
|
|
110
101
|
```
|
|
111
102
|
|
|
112
|
-
The script
|
|
113
|
-
|
|
114
|
-
**Parse the JSON output and set context flags for step 4:**
|
|
103
|
+
The script removes the `qmd_collections` entries no live collection matches, reading the live names from section 2's output; with no such output (below Deep tier, or a classifier that failed) it leaves `qmd_collections` alone.
|
|
115
104
|
|
|
116
|
-
- `{
|
|
117
|
-
- `{ccc_registry_stale_cleaned}` ← `len(ccc_removed)`
|
|
118
|
-
- `{ccc_registry_stale_removed_paths}` ← `ccc_removed` (the list — step 4 folds individual paths into envelope warnings)
|
|
105
|
+
**If the script exits non-zero:** the registry stays as it was. Unless `{quiet_mode}` is true, display one line: "Registry cleanup skipped: {message}.", where `{message}` is the `message` of the stderr JSON `{"status":"error","message":...}`. Then continue to section 5: hygiene errors never fail the workflow.
|
|
119
106
|
|
|
120
|
-
|
|
107
|
+
**Otherwise**, when `qmd_removed` is non-empty and `{quiet_mode}` is false, display: "**Cleaned {n} stale QMD registry entry/entries** (collection no longer exists in QMD).", where `{n}` is the number of names in `qmd_removed`.
|
|
121
108
|
|
|
122
|
-
|
|
109
|
+
Removed ccc registry paths are not displayed here: the step 4 report's CCC Registry line counts them, and the envelope's `warnings` lists each one.
|
|
123
110
|
|
|
124
111
|
### 5. Auto-Proceed
|
|
125
112
|
|
|
126
|
-
|
|
113
|
+
Load `{nextStepFile}`, read it fully, and execute it.
|