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,55 +1,105 @@
|
|
|
1
1
|
# /// script
|
|
2
|
-
# requires-python = ">=3.
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
3
|
# dependencies = ["pyyaml"]
|
|
4
4
|
# ///
|
|
5
|
-
"""Campaign Render Batch
|
|
5
|
+
"""Campaign Render Batch: build the QS `--batch` input file for Tier B skills.
|
|
6
6
|
|
|
7
7
|
step-06 batches every Tier B skill through `skf-quick-skill --batch`. The batch
|
|
8
8
|
input file is a machine-consumed cross-tool contract: one target per line in the
|
|
9
|
-
exact single-target shape `skf-quick-skill` parses (
|
|
10
|
-
|
|
11
|
-
is filter + join + reformat-structured-data
|
|
12
|
-
work campaign-parse-manifest.py owns for the
|
|
13
|
-
format slip corrupts the whole run. This
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
exact single-target shape `skf-quick-skill` parses (parse_line in the shared
|
|
10
|
+
skf-quick-batch.py, documented in quick-skill's references/batch-mode.md).
|
|
11
|
+
Hand-building that file in prose is filter + join + reformat-structured-data,
|
|
12
|
+
the same mechanical, all-or-nothing work campaign-parse-manifest.py owns for the
|
|
13
|
+
inverse direction, where a silent format slip corrupts the whole run. This
|
|
14
|
+
script is the single source of truth for
|
|
15
|
+
the generation side, and for the way back: it is the only place step-06 selects
|
|
16
|
+
Tier B skills and the only place it joins QS results to them.
|
|
17
|
+
|
|
18
|
+
What it does (render mode):
|
|
19
|
+
- Selects `skills[]` with `tier == "B"` and status `pending` or `active` (an
|
|
20
|
+
`active` skill is one an interrupted batch left behind; completed, failed
|
|
21
|
+
and skipped skills are left alone, so a resume is safe).
|
|
22
|
+
- Leaves out each selected skill the directive's `## Skip List` names (read
|
|
23
|
+
through campaign-quality-gate.py) and reports it in `skipped_by_directive`.
|
|
20
24
|
- Looks up each skill's `repo_url` from the brief's `targets[]` (matched by
|
|
21
25
|
name); repo URLs live only in the brief, never in the state schema.
|
|
22
26
|
- Emits one line per skill in the CONSUMER's single-target shape:
|
|
23
|
-
{repo_url} (pin is null
|
|
24
|
-
{repo_url}@{pin} (
|
|
27
|
+
{repo_url} (pin is null: latest)
|
|
28
|
+
{repo_url}@{pin} (a version pin: a digit, or `v` and a
|
|
29
|
+
digit, first; skf-resolve-package.py's
|
|
30
|
+
_VERSION_RE)
|
|
31
|
+
{repo_url}/tree/{pin} (any other pin, a branch such as
|
|
32
|
+
`main`: quick-skill's parse-target
|
|
33
|
+
reads it as the ref to build)
|
|
25
34
|
with optional ` language=<lang>` / ` scope=<path>` modifiers appended when the
|
|
26
35
|
brief target carries a `language_hint`/`language` or `scope_hint`/`scope` hint.
|
|
27
|
-
|
|
36
|
+
QS reads a modifier as one word: a hint that is empty or holds whitespace
|
|
37
|
+
would make it read the whole line as one target, so it is left out and
|
|
38
|
+
reported in `dropped_hints`, and the first word of every line is the
|
|
39
|
+
target QS records for it.
|
|
40
|
+
- No skill-name field, no bare pin token: that would not parse as a target.
|
|
41
|
+
- With --map, writes the line-to-skill map: line n of the batch file is QS's
|
|
42
|
+
batch number n, so two skills whose lines read the same stay apart.
|
|
43
|
+
|
|
44
|
+
Record mode (--record): joins the QS batch summary (`summary_path` of the
|
|
45
|
+
`batch_summary` event) to the map by batch number. Each mapped skill gets
|
|
46
|
+
`completed` (QS status success, `skill_path` from `skill_package`, its
|
|
47
|
+
`quality_score`) or `failed` (with QS's `error_code`, or `no-batch-result`
|
|
48
|
+
when the summary holds no result for its line). A result whose target is not
|
|
49
|
+
the mapped line's target means the summary belongs to another batch file, and
|
|
50
|
+
nothing is joined.
|
|
28
51
|
|
|
29
52
|
CLI:
|
|
30
|
-
uv run campaign-render-batch.py --state-file <p> --brief-file <p>
|
|
53
|
+
uv run campaign-render-batch.py --state-file <p> --brief-file <p> \
|
|
54
|
+
[-o <batchFile>] [--map <mapFile>] [--directive-file <p>]
|
|
55
|
+
uv run campaign-render-batch.py --record <summary_path> --map <mapFile>
|
|
31
56
|
|
|
32
57
|
Output:
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
render: batch text (one target per line) to {batchFile} via -o, else to
|
|
59
|
+
stdout; the map JSON {"batch_file", "lines": [{"batch", "skill",
|
|
60
|
+
"target", "line"}], "skipped_by_directive": [{"name", "reason"}]} to
|
|
61
|
+
--map; a JSON summary {written, count, skipped_non_tierB,
|
|
62
|
+
skipped_non_pending, skills, skipped_by_directive, dropped_hints:
|
|
63
|
+
[{"skill", "hint", "value"}]} to stderr.
|
|
64
|
+
record: {"results": [{"skill", "batch", "status", "quality_score",
|
|
65
|
+
"skill_path", "exit_code", "error_code"}], "counts": {"completed",
|
|
66
|
+
"failed"}} on stdout.
|
|
35
67
|
|
|
36
68
|
Exit codes:
|
|
37
|
-
0 batch file written (0+ targets)
|
|
38
|
-
2 state file missing / unreadable / bad YAML
|
|
39
|
-
|
|
69
|
+
0 batch file written (0+ targets), or results joined
|
|
70
|
+
2 state file missing / unreadable / bad YAML, a directive that cannot be
|
|
71
|
+
read, a missing or unreadable map or summary, or a summary that does not
|
|
72
|
+
match the map (SUMMARY_MISMATCH)
|
|
73
|
+
8 brief missing / unreadable (missing-brief HALT), or a selected Tier B skill
|
|
40
74
|
has no matching brief target with a repo_url (unmatched-target)
|
|
41
75
|
"""
|
|
42
76
|
|
|
43
77
|
from __future__ import annotations
|
|
44
78
|
|
|
45
79
|
import argparse
|
|
80
|
+
import importlib.util
|
|
46
81
|
import json
|
|
47
82
|
import sys
|
|
48
83
|
from pathlib import Path
|
|
49
|
-
from typing import Any, Dict, List, Tuple
|
|
84
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
50
85
|
|
|
51
86
|
import yaml
|
|
52
87
|
|
|
88
|
+
GATE_SCRIPT = Path(__file__).resolve().with_name("campaign-quality-gate.py")
|
|
89
|
+
RESOLVE_PACKAGE_SCRIPT = Path(__file__).resolve().parent.parent.parent / "shared" / "scripts" / "skf-resolve-package.py"
|
|
90
|
+
SELECTED_STATUSES = ("pending", "active")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _version_re() -> Any:
|
|
94
|
+
"""skf-resolve-package.py's _VERSION_RE: the pins parse-target reads after `@`."""
|
|
95
|
+
spec = importlib.util.spec_from_file_location("skf_resolve_package", RESOLVE_PACKAGE_SCRIPT)
|
|
96
|
+
module = importlib.util.module_from_spec(spec)
|
|
97
|
+
spec.loader.exec_module(module)
|
|
98
|
+
return module._VERSION_RE
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
VERSION_RE = _version_re()
|
|
102
|
+
|
|
53
103
|
|
|
54
104
|
def _err(message: str, code: str, exit_code: int, **extra: Any) -> int:
|
|
55
105
|
payload: Dict[str, Any] = {"error": message, "code": code}
|
|
@@ -59,27 +109,58 @@ def _err(message: str, code: str, exit_code: int, **extra: Any) -> int:
|
|
|
59
109
|
return exit_code
|
|
60
110
|
|
|
61
111
|
|
|
62
|
-
def _target_line(repo_url: str, pin: Any, target: Dict[str, Any]) -> str:
|
|
63
|
-
"""Render one batch line in the consumer's single-target shape.
|
|
112
|
+
def _target_line(repo_url: str, pin: Any, target: Dict[str, Any]) -> Tuple[str, List[Dict[str, Any]]]:
|
|
113
|
+
"""Render one batch line in the consumer's single-target shape.
|
|
114
|
+
|
|
115
|
+
Returns (line, dropped): each hint QS could not read as one modifier word
|
|
116
|
+
(empty, or holding whitespace) is left off the line and returned instead.
|
|
117
|
+
"""
|
|
64
118
|
line = repo_url
|
|
65
|
-
if pin:
|
|
119
|
+
if pin and VERSION_RE.match(str(pin)):
|
|
66
120
|
line += f"@{pin}"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
121
|
+
elif pin:
|
|
122
|
+
# parse-target reads `@main` as no target at all; /tree/<branch> is a ref.
|
|
123
|
+
line += f"/tree/{pin}"
|
|
124
|
+
dropped: List[Dict[str, Any]] = []
|
|
125
|
+
for modifier, fields in (("language", ("language_hint", "language")), ("scope", ("scope_hint", "scope"))):
|
|
126
|
+
field = next((f for f in fields if target.get(f) not in (None, "")), None)
|
|
127
|
+
if field is None:
|
|
128
|
+
continue
|
|
129
|
+
value = str(target[field]).strip()
|
|
130
|
+
if not value or any(c.isspace() for c in value):
|
|
131
|
+
dropped.append({"hint": field, "value": target[field]})
|
|
132
|
+
continue
|
|
133
|
+
line += f" {modifier}={value}"
|
|
134
|
+
return line, dropped
|
|
75
135
|
|
|
76
|
-
def build_batch(state: Dict[str, Any], brief: Dict[str, Any]) -> Tuple[List[str], Dict[str, Any]]:
|
|
77
|
-
"""Filter Tier B pending skills and render batch lines.
|
|
78
136
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
137
|
+
def _skip_list(directive_file: Optional[str]) -> Dict[str, Optional[str]]:
|
|
138
|
+
"""The directive's Skip List, parsed by campaign-quality-gate.py (one parser)."""
|
|
139
|
+
if not directive_file:
|
|
140
|
+
return {}
|
|
141
|
+
spec = importlib.util.spec_from_file_location("campaign_quality_gate", GATE_SCRIPT)
|
|
142
|
+
gate = importlib.util.module_from_spec(spec)
|
|
143
|
+
spec.loader.exec_module(gate)
|
|
144
|
+
try:
|
|
145
|
+
return gate.read_directive(directive_file)["skip"]
|
|
146
|
+
except gate.GateError as exc:
|
|
147
|
+
raise OSError(str(exc)) from exc
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def build_batch(
|
|
151
|
+
state: Dict[str, Any],
|
|
152
|
+
brief: Dict[str, Any],
|
|
153
|
+
skip: Optional[Dict[str, Optional[str]]] = None,
|
|
154
|
+
) -> Tuple[List[str], Dict[str, Any]]:
|
|
155
|
+
"""Select Tier B pending and interrupted skills and render batch lines.
|
|
156
|
+
|
|
157
|
+
Returns (lines, summary). `summary["skills"]` names the skill of each line,
|
|
158
|
+
in order; `summary["skipped_by_directive"]` lists the selected skills the
|
|
159
|
+
Skip List names; `summary["unmatched"]` lists any selected Tier B skill
|
|
160
|
+
with no brief target / repo_url; the caller HALTs (exit 8) when it is
|
|
161
|
+
non-empty.
|
|
82
162
|
"""
|
|
163
|
+
skip = skip or {}
|
|
83
164
|
skills = state.get("skills") or []
|
|
84
165
|
targets: Dict[str, Dict[str, Any]] = {
|
|
85
166
|
t.get("name"): t
|
|
@@ -88,7 +169,10 @@ def build_batch(state: Dict[str, Any], brief: Dict[str, Any]) -> Tuple[List[str]
|
|
|
88
169
|
}
|
|
89
170
|
|
|
90
171
|
lines: List[str] = []
|
|
172
|
+
names: List[str] = []
|
|
91
173
|
unmatched: List[str] = []
|
|
174
|
+
skipped_by_directive: List[Dict[str, Any]] = []
|
|
175
|
+
dropped_hints: List[Dict[str, Any]] = []
|
|
92
176
|
skipped_non_tierb = 0
|
|
93
177
|
skipped_non_pending = 0
|
|
94
178
|
|
|
@@ -98,28 +182,94 @@ def build_batch(state: Dict[str, Any], brief: Dict[str, Any]) -> Tuple[List[str]
|
|
|
98
182
|
if skill.get("tier") != "B":
|
|
99
183
|
skipped_non_tierb += 1
|
|
100
184
|
continue
|
|
101
|
-
if skill.get("status")
|
|
185
|
+
if skill.get("status") not in SELECTED_STATUSES:
|
|
102
186
|
skipped_non_pending += 1
|
|
103
187
|
continue
|
|
104
188
|
|
|
105
189
|
name = skill.get("name")
|
|
190
|
+
if name in skip:
|
|
191
|
+
skipped_by_directive.append({"name": name, "reason": skip[name]})
|
|
192
|
+
continue
|
|
106
193
|
target = targets.get(name)
|
|
107
194
|
repo_url = (target or {}).get("repo_url") or ""
|
|
108
195
|
if not repo_url:
|
|
109
196
|
unmatched.append(name)
|
|
110
197
|
continue
|
|
111
198
|
|
|
112
|
-
|
|
199
|
+
line, dropped = _target_line(repo_url, skill.get("pin"), target)
|
|
200
|
+
lines.append(line)
|
|
201
|
+
names.append(name)
|
|
202
|
+
dropped_hints.extend({"skill": name, **d} for d in dropped)
|
|
113
203
|
|
|
114
204
|
summary = {
|
|
115
205
|
"count": len(lines),
|
|
116
206
|
"skipped_non_tierB": skipped_non_tierb,
|
|
117
207
|
"skipped_non_pending": skipped_non_pending,
|
|
208
|
+
"skills": names,
|
|
209
|
+
"skipped_by_directive": skipped_by_directive,
|
|
210
|
+
"dropped_hints": dropped_hints,
|
|
118
211
|
"unmatched": unmatched,
|
|
119
212
|
}
|
|
120
213
|
return lines, summary
|
|
121
214
|
|
|
122
215
|
|
|
216
|
+
def build_map(lines: List[str], summary: Dict[str, Any], batch_file: str) -> Dict[str, Any]:
|
|
217
|
+
"""The line-to-skill map: QS numbers the batch file's targets from 1, in line order.
|
|
218
|
+
|
|
219
|
+
A line's target is its first word, the target QS records for it, because
|
|
220
|
+
every word after it is a modifier QS reads as one (see _target_line).
|
|
221
|
+
"""
|
|
222
|
+
return {
|
|
223
|
+
"batch_file": batch_file,
|
|
224
|
+
"lines": [
|
|
225
|
+
{"batch": n, "skill": name, "target": line.split()[0], "line": line}
|
|
226
|
+
for n, (line, name) in enumerate(zip(lines, summary["skills"]), start=1)
|
|
227
|
+
],
|
|
228
|
+
"skipped_by_directive": summary["skipped_by_directive"],
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def join_results(batch_map: Dict[str, Any], batch_summary: Dict[str, Any]) -> Dict[str, Any]:
|
|
233
|
+
"""Join a QS batch summary's results[] to the mapped skills by batch number.
|
|
234
|
+
|
|
235
|
+
Raises ValueError when a result's target is not its mapped line's target.
|
|
236
|
+
"""
|
|
237
|
+
by_batch = {
|
|
238
|
+
r.get("batch"): r
|
|
239
|
+
for r in (batch_summary.get("results") or [])
|
|
240
|
+
if isinstance(r, dict)
|
|
241
|
+
}
|
|
242
|
+
results: List[Dict[str, Any]] = []
|
|
243
|
+
for entry in batch_map.get("lines") or []:
|
|
244
|
+
result = by_batch.get(entry["batch"])
|
|
245
|
+
if result is not None and result.get("target") != entry["target"]:
|
|
246
|
+
raise ValueError(
|
|
247
|
+
f"batch {entry['batch']} is `{result.get('target')}` in the summary but "
|
|
248
|
+
f"`{entry['target']}` in the map: the summary belongs to another batch file"
|
|
249
|
+
)
|
|
250
|
+
row: Dict[str, Any] = {
|
|
251
|
+
"skill": entry["skill"],
|
|
252
|
+
"batch": entry["batch"],
|
|
253
|
+
"status": "failed",
|
|
254
|
+
"quality_score": None,
|
|
255
|
+
"skill_path": None,
|
|
256
|
+
"exit_code": None,
|
|
257
|
+
"error_code": "no-batch-result",
|
|
258
|
+
}
|
|
259
|
+
if result is not None:
|
|
260
|
+
ok = result.get("status") == "success"
|
|
261
|
+
row.update(
|
|
262
|
+
status="completed" if ok else "failed",
|
|
263
|
+
quality_score=result.get("quality_score") if ok else None,
|
|
264
|
+
skill_path=result.get("skill_package") if ok else None,
|
|
265
|
+
exit_code=result.get("exit_code"),
|
|
266
|
+
error_code=None if ok else result.get("error_code"),
|
|
267
|
+
)
|
|
268
|
+
results.append(row)
|
|
269
|
+
counts = {key: sum(1 for r in results if r["status"] == key) for key in ("completed", "failed")}
|
|
270
|
+
return {"results": results, "counts": counts}
|
|
271
|
+
|
|
272
|
+
|
|
123
273
|
def _load_yaml_mapping(path: str) -> Dict[str, Any]:
|
|
124
274
|
data = yaml.safe_load(Path(path).read_text(encoding="utf-8"))
|
|
125
275
|
if data is None:
|
|
@@ -129,7 +279,13 @@ def _load_yaml_mapping(path: str) -> Dict[str, Any]:
|
|
|
129
279
|
return data
|
|
130
280
|
|
|
131
281
|
|
|
132
|
-
def run(
|
|
282
|
+
def run(
|
|
283
|
+
state_file: str,
|
|
284
|
+
brief_file: str,
|
|
285
|
+
output: str | None,
|
|
286
|
+
map_file: str | None = None,
|
|
287
|
+
directive_file: str | None = None,
|
|
288
|
+
) -> int:
|
|
133
289
|
# State problems are plain file/parse errors (exit 2).
|
|
134
290
|
if not Path(state_file).is_file():
|
|
135
291
|
return _err(f"State file not found: {state_file}", "STATE_NOT_FOUND", 2)
|
|
@@ -138,7 +294,7 @@ def run(state_file: str, brief_file: str, output: str | None) -> int:
|
|
|
138
294
|
except (yaml.YAMLError, ValueError) as exc:
|
|
139
295
|
return _err(f"Failed to parse state YAML: {exc}", "STATE_PARSE_ERROR", 2)
|
|
140
296
|
|
|
141
|
-
# Brief problems preserve step-06
|
|
297
|
+
# Brief problems preserve step-06's missing-brief HALT (exit 8): repo_urls
|
|
142
298
|
# live only in the brief, so a missing/unreadable/corrupt brief is fatal here.
|
|
143
299
|
if not Path(brief_file).is_file():
|
|
144
300
|
return _err(f"Brief file not found: {brief_file}", "missing-brief", 8)
|
|
@@ -149,12 +305,17 @@ def run(state_file: str, brief_file: str, output: str | None) -> int:
|
|
|
149
305
|
except (yaml.YAMLError, ValueError) as exc:
|
|
150
306
|
return _err(f"Brief unparseable: {exc}", "missing-brief", 8)
|
|
151
307
|
|
|
152
|
-
|
|
308
|
+
try:
|
|
309
|
+
skip = _skip_list(directive_file)
|
|
310
|
+
except OSError as exc:
|
|
311
|
+
return _err(str(exc), "DIRECTIVE_UNREADABLE", 2)
|
|
312
|
+
|
|
313
|
+
lines, summary = build_batch(state, brief, skip)
|
|
153
314
|
|
|
154
315
|
unmatched = summary.pop("unmatched")
|
|
155
316
|
if unmatched:
|
|
156
317
|
return _err(
|
|
157
|
-
"No matching brief target (repo_url) for
|
|
318
|
+
"No matching brief target (repo_url) for selected Tier B skill(s): "
|
|
158
319
|
+ ", ".join(str(n) for n in unmatched),
|
|
159
320
|
"unmatched-target",
|
|
160
321
|
8,
|
|
@@ -177,22 +338,69 @@ def run(state_file: str, brief_file: str, output: str | None) -> int:
|
|
|
177
338
|
"count": summary["count"],
|
|
178
339
|
"skipped_non_tierB": summary["skipped_non_tierB"],
|
|
179
340
|
"skipped_non_pending": summary["skipped_non_pending"],
|
|
341
|
+
"skills": summary["skills"],
|
|
342
|
+
"skipped_by_directive": summary["skipped_by_directive"],
|
|
343
|
+
"dropped_hints": summary["dropped_hints"],
|
|
180
344
|
}
|
|
345
|
+
if map_file:
|
|
346
|
+
Path(map_file).write_text(
|
|
347
|
+
json.dumps(build_map(lines, summary, summary["written"]), indent=2) + "\n",
|
|
348
|
+
encoding="utf-8",
|
|
349
|
+
)
|
|
350
|
+
ordered["map"] = map_file
|
|
181
351
|
json.dump(ordered, sys.stderr)
|
|
182
352
|
sys.stderr.write("\n")
|
|
183
353
|
return 0
|
|
184
354
|
|
|
185
355
|
|
|
356
|
+
def _load_json(path: str, label: str) -> Tuple[Optional[Dict[str, Any]], int]:
|
|
357
|
+
if not Path(path).is_file():
|
|
358
|
+
return None, _err(f"{label.capitalize()} file not found: {path}", f"{label.upper()}_NOT_FOUND", 2)
|
|
359
|
+
try:
|
|
360
|
+
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
361
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
362
|
+
return None, _err(f"{label.capitalize()} unreadable: {exc}", f"{label.upper()}_PARSE_ERROR", 2)
|
|
363
|
+
if not isinstance(data, dict):
|
|
364
|
+
return None, _err(f"{label.capitalize()} is not a JSON object: {path}", f"{label.upper()}_PARSE_ERROR", 2)
|
|
365
|
+
return data, 0
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def record(summary_file: str, map_file: str) -> int:
|
|
369
|
+
batch_map, rc = _load_json(map_file, "map")
|
|
370
|
+
if batch_map is None:
|
|
371
|
+
return rc
|
|
372
|
+
batch_summary, rc = _load_json(summary_file, "summary")
|
|
373
|
+
if batch_summary is None:
|
|
374
|
+
return rc
|
|
375
|
+
try:
|
|
376
|
+
joined = join_results(batch_map, batch_summary)
|
|
377
|
+
except ValueError as exc:
|
|
378
|
+
return _err(str(exc), "SUMMARY_MISMATCH", 2)
|
|
379
|
+
json.dump(joined, sys.stdout, separators=(",", ":"))
|
|
380
|
+
sys.stdout.write("\n")
|
|
381
|
+
return 0
|
|
382
|
+
|
|
383
|
+
|
|
186
384
|
def main(argv: list[str] | None = None) -> int:
|
|
187
385
|
parser = argparse.ArgumentParser(
|
|
188
386
|
prog="campaign-render-batch",
|
|
189
|
-
description="Build the QS --batch input file for
|
|
387
|
+
description="Build the QS --batch input file for Tier B skills, or join a QS "
|
|
388
|
+
"batch summary back to them with --record.",
|
|
190
389
|
)
|
|
191
|
-
parser.add_argument("--state-file"
|
|
192
|
-
parser.add_argument("--brief-file"
|
|
390
|
+
parser.add_argument("--state-file")
|
|
391
|
+
parser.add_argument("--brief-file")
|
|
193
392
|
parser.add_argument("-o", "--output", dest="output", help="batch file path (default: stdout)")
|
|
393
|
+
parser.add_argument("--map", dest="map_file", help="line-to-skill map path (written, or read by --record)")
|
|
394
|
+
parser.add_argument("--directive-file", help="campaign directive whose Skip List is applied")
|
|
395
|
+
parser.add_argument("--record", dest="summary_file", help="QS batch summary to join to --map")
|
|
194
396
|
args = parser.parse_args(argv)
|
|
195
|
-
|
|
397
|
+
if args.summary_file:
|
|
398
|
+
if not args.map_file:
|
|
399
|
+
parser.error("--record needs --map")
|
|
400
|
+
return record(args.summary_file, args.map_file)
|
|
401
|
+
if not args.state_file or not args.brief_file:
|
|
402
|
+
parser.error("--state-file and --brief-file are required to build the batch file")
|
|
403
|
+
return run(args.state_file, args.brief_file, args.output, args.map_file, args.directive_file)
|
|
196
404
|
|
|
197
405
|
|
|
198
406
|
if __name__ == "__main__":
|