bmad-module-skill-forge 2.2.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 +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +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 +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +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 +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +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 +15 -5
- 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 +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +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 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +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 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""Deterministic circuit-breaker decision for one finished pipeline step.
|
|
7
|
+
|
|
8
|
+
Pipeline Mode (references/pipeline-mode.md step 4d) runs this after AN, TS, AS
|
|
9
|
+
or VS finishes, instead of reading the step's result and comparing it by
|
|
10
|
+
hand. Which field carries the verdict, and which verdicts go on, skip or stop,
|
|
11
|
+
has one correct answer per result, so it runs here, not in the prompt. The
|
|
12
|
+
gate never compares a TS score with a threshold: the forger hands a `[min:N]`
|
|
13
|
+
to TS as `--threshold=N`, and TS settles its own verdict, caps and 80% floor
|
|
14
|
+
fallback included.
|
|
15
|
+
|
|
16
|
+
The rules mirror the Circuit Breakers table in
|
|
17
|
+
`src/shared/references/pipeline-contracts.md`:
|
|
18
|
+
|
|
19
|
+
TS continue only on the settled verdict PASS, which TS routes to export
|
|
20
|
+
(`next_workflow` is `export-skill`). FAIL, INCONCLUSIVE and
|
|
21
|
+
pass-with-drift halt with the verdict as the reason; so does a FAIL
|
|
22
|
+
that a post-score cap forced while the score clears the threshold.
|
|
23
|
+
AN continue when it confirmed at least `--min` skillable units (default 1).
|
|
24
|
+
A redirect or a skipped target halts: there is no brief to go on with.
|
|
25
|
+
AS CRITICAL drift halts; CLEAN skips an update that comes next
|
|
26
|
+
(`--next US`) unless AS routes to it (`next_workflow` is
|
|
27
|
+
`update-skill`: upstream moved past the skill's ref, so an update is
|
|
28
|
+
due however clean the audited tree read); MINOR and SIGNIFICANT
|
|
29
|
+
continue.
|
|
30
|
+
VS zero coverage halts (`zero-coverage`): VS verified none of the
|
|
31
|
+
architecture's technologies, so nothing after it has evidence to use.
|
|
32
|
+
Every verdict with coverage continues, NOT_FEASIBLE included: RA, which
|
|
33
|
+
usually follows VS, takes each Blocked integration as a critical issue.
|
|
34
|
+
|
|
35
|
+
A result with `status` `error` or `failed` halts with its `halt_reason`, or
|
|
36
|
+
its status when it names none, a TS PASS included. A TS result with any other
|
|
37
|
+
verdict keeps the verdict as the reason, so a hard-gate block halts as FAIL. A
|
|
38
|
+
result the gate cannot read, or a value outside its rule's set, halts too: a
|
|
39
|
+
gate that cannot decide never lets a step through.
|
|
40
|
+
|
|
41
|
+
Input, on stdin (or in the file `--result` names, for tests and CI): the
|
|
42
|
+
step's result envelope line (`SKF_TEST_RESULT_JSON: {...}` and the like; the
|
|
43
|
+
last line of the code's own envelope wins, and one from another workflow is
|
|
44
|
+
refused) or a result record. Pipeline Mode gates only the envelope line the
|
|
45
|
+
step just printed, and an empty input when it printed none (`no-result`):
|
|
46
|
+
a `-latest.json` record may not describe this run, because a workflow can
|
|
47
|
+
halt after writing it (TS writes its record before its last halts) or abort
|
|
48
|
+
before writing one. The envelope and the record name the fields differently,
|
|
49
|
+
and both are read:
|
|
50
|
+
|
|
51
|
+
TS verdict `verdict` | `summary.result` (PASS_WITH_DRIFT reads as
|
|
52
|
+
pass-with-drift), route `next_workflow` (checked when present)
|
|
53
|
+
AN units `unit_counts.confirmed`, else the `brief_paths`, else the
|
|
54
|
+
`skill-brief.yaml` paths in `outputs[]`
|
|
55
|
+
AS severity `drift_score` | `summary.severity`, route `next_workflow` |
|
|
56
|
+
`summary.next_workflow`, handoff `upstream_moved` and `upstream_ref`
|
|
57
|
+
(or their `summary.` copies)
|
|
58
|
+
VS verdict `overall_verdict` | `summary.overallVerdict`, coverage
|
|
59
|
+
`coverage_percentage` | `summary.coveragePercentage`
|
|
60
|
+
|
|
61
|
+
The input is read as UTF-8 and a byte-order mark is dropped; input that is
|
|
62
|
+
not UTF-8 halts as `result-unreadable`.
|
|
63
|
+
|
|
64
|
+
CLI usage:
|
|
65
|
+
uv run scripts/pipeline-gate.py --code TS --next EX <<'SKF_RESULT'
|
|
66
|
+
SKF_TEST_RESULT_JSON: {"status":"success","verdict":"FAIL",...}
|
|
67
|
+
SKF_RESULT
|
|
68
|
+
uv run scripts/pipeline-gate.py --code AN --min 2 <<'SKF_RESULT'
|
|
69
|
+
SKF_RESULT
|
|
70
|
+
uv run scripts/pipeline-gate.py --code AS --next US --result <saved-envelope.txt>
|
|
71
|
+
|
|
72
|
+
(The AN call has an empty heredoc: the step printed no envelope, so the
|
|
73
|
+
gate halts with `no-result`.)
|
|
74
|
+
|
|
75
|
+
Output (stdout, one object):
|
|
76
|
+
{
|
|
77
|
+
"code": "TS",
|
|
78
|
+
"decision": "continue" | "skip" | "halt",
|
|
79
|
+
"reason": null | "<why it skips or halts>", # TS: FAIL, INCONCLUSIVE, pass-with-drift
|
|
80
|
+
"skip": null | "US", # the next workflow a skip passes over
|
|
81
|
+
"message": "<one line for the pipeline report>",
|
|
82
|
+
"handoff": null | {"target_ref": "<ref>"} # what the step hands the next, by Data Flow name
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
`handoff` is set only after an AS whose result says upstream moved
|
|
86
|
+
(`upstream_moved` true) and names the new ref (`upstream_ref`): a continue,
|
|
87
|
+
or a CRITICAL halt, whose review-drift-report repair picks the chain up at
|
|
88
|
+
US. `pipeline-journal.py step --gate` records it, and US takes it as
|
|
89
|
+
`--target-ref`, so the update reads the ref the audit found.
|
|
90
|
+
|
|
91
|
+
Exit codes:
|
|
92
|
+
0 a decision was printed (continue, skip or halt: read `decision`)
|
|
93
|
+
2 usage error (argparse: a missing or unknown argument, usage on stderr,
|
|
94
|
+
no JSON)
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
from __future__ import annotations
|
|
98
|
+
|
|
99
|
+
import argparse
|
|
100
|
+
import json
|
|
101
|
+
import re
|
|
102
|
+
import sys
|
|
103
|
+
from pathlib import Path
|
|
104
|
+
|
|
105
|
+
# The envelope each gated workflow prints in headless mode.
|
|
106
|
+
ENVELOPES = {
|
|
107
|
+
"AN": "SKF_ANALYZE_RESULT_JSON",
|
|
108
|
+
"TS": "SKF_TEST_RESULT_JSON",
|
|
109
|
+
"AS": "SKF_AUDIT_RESULT_JSON",
|
|
110
|
+
"VS": "SKF_VERIFY_STACK_RESULT_JSON",
|
|
111
|
+
}
|
|
112
|
+
GATED_CODES = tuple(ENVELOPES)
|
|
113
|
+
|
|
114
|
+
# Settled test-skill verdicts, keyed by their normalized spelling; the value
|
|
115
|
+
# is the envelope's spelling, which a halt reports as its reason.
|
|
116
|
+
TS_VERDICTS = {
|
|
117
|
+
"PASS": "PASS",
|
|
118
|
+
"FAIL": "FAIL",
|
|
119
|
+
"INCONCLUSIVE": "INCONCLUSIVE",
|
|
120
|
+
"PASS_WITH_DRIFT": "pass-with-drift",
|
|
121
|
+
}
|
|
122
|
+
AS_SEVERITIES = ("CLEAN", "MINOR", "SIGNIFICANT", "CRITICAL")
|
|
123
|
+
VS_VERDICTS = ("FEASIBLE", "CONDITIONALLY_FEASIBLE", "NOT_FEASIBLE")
|
|
124
|
+
ERROR_STATUSES = frozenset({"error", "failed"})
|
|
125
|
+
EXPORT_ROUTE = "export-skill"
|
|
126
|
+
UPDATE_ROUTE = "update-skill"
|
|
127
|
+
|
|
128
|
+
_ENVELOPE_RE = re.compile(r"(SKF_[A-Z0-9_]+_RESULT_JSON):\s*(\{.*\})\s*$")
|
|
129
|
+
_MISSING = object()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# --------------------------------------------------------------------------
|
|
133
|
+
# Reading the result
|
|
134
|
+
# --------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def read_result(code: str, text: str | None) -> tuple[dict | None, str | None]:
|
|
138
|
+
"""Return (result, problem) for the text a step produced.
|
|
139
|
+
|
|
140
|
+
The last envelope line of `code`'s own workflow wins. Envelope lines of
|
|
141
|
+
other workflows alone are refused (`wrong-result`); with no envelope line
|
|
142
|
+
at all, the whole text must be one JSON object (a result record).
|
|
143
|
+
"""
|
|
144
|
+
if text is None:
|
|
145
|
+
return None, "result-unreadable"
|
|
146
|
+
if not text.strip():
|
|
147
|
+
return None, "no-result"
|
|
148
|
+
found = [m for m in map(_ENVELOPE_RE.search, text.splitlines()) if m]
|
|
149
|
+
own = [m.group(2) for m in found if m.group(1) == ENVELOPES[code]]
|
|
150
|
+
if found and not own:
|
|
151
|
+
return None, "wrong-result"
|
|
152
|
+
try:
|
|
153
|
+
value = json.loads(own[-1] if own else text)
|
|
154
|
+
except json.JSONDecodeError:
|
|
155
|
+
return None, "result-unreadable"
|
|
156
|
+
if not isinstance(value, dict):
|
|
157
|
+
return None, "result-unreadable"
|
|
158
|
+
return value, None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _field(result: dict, key: str, summary_key: str):
|
|
162
|
+
"""The envelope's `key`, else the record's `summary.<summary_key>`."""
|
|
163
|
+
value = result.get(key)
|
|
164
|
+
if value is None:
|
|
165
|
+
summary = result.get("summary")
|
|
166
|
+
if isinstance(summary, dict):
|
|
167
|
+
value = summary.get(summary_key)
|
|
168
|
+
return value
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _token(value) -> str | None:
|
|
172
|
+
"""A verdict or severity as one upper-case token: `pass-with-drift` -> PASS_WITH_DRIFT."""
|
|
173
|
+
if not isinstance(value, str) or not value.strip():
|
|
174
|
+
return None
|
|
175
|
+
return value.strip().upper().replace("-", "_")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _is_error(result: dict) -> bool:
|
|
179
|
+
status = result.get("status")
|
|
180
|
+
return isinstance(status, str) and status.strip().lower() in ERROR_STATUSES
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _halt_reason(result: dict) -> str:
|
|
184
|
+
"""The reason an errored result names, or its status."""
|
|
185
|
+
summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
|
|
186
|
+
for value in (result.get("halt_reason"), summary.get("halt_reason")):
|
|
187
|
+
if isinstance(value, str) and value.strip():
|
|
188
|
+
return value.strip()
|
|
189
|
+
return str(result.get("status")).strip().lower()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _count(value) -> int | None:
|
|
193
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
194
|
+
return None
|
|
195
|
+
return value
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _percentage(value) -> int | float | None:
|
|
199
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
200
|
+
return None
|
|
201
|
+
return value if 0 <= value <= 100 else None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _units(result: dict) -> int | None:
|
|
205
|
+
"""Confirmed skillable units: the envelope count, else the briefs listed."""
|
|
206
|
+
counts = result.get("unit_counts")
|
|
207
|
+
if isinstance(counts, dict) and _count(counts.get("confirmed")) is not None:
|
|
208
|
+
return counts["confirmed"]
|
|
209
|
+
briefs = result.get("brief_paths")
|
|
210
|
+
if isinstance(briefs, list):
|
|
211
|
+
return len(briefs)
|
|
212
|
+
outputs = result.get("outputs")
|
|
213
|
+
if isinstance(outputs, list):
|
|
214
|
+
paths = [o.get("path") if isinstance(o, dict) else o for o in outputs]
|
|
215
|
+
return sum(isinstance(p, str) and p.endswith("skill-brief.yaml") for p in paths)
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# --------------------------------------------------------------------------
|
|
220
|
+
# The rules
|
|
221
|
+
# --------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _decision(code, decision, reason=None, message="", skip=None, handoff=None):
|
|
225
|
+
return {"code": code, "decision": decision, "reason": reason, "skip": skip, "message": message,
|
|
226
|
+
"handoff": handoff}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _halt(code, reason, message, handoff=None):
|
|
230
|
+
return _decision(code, "halt", reason, message, handoff=handoff)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _go(code, message, handoff=None):
|
|
234
|
+
return _decision(code, "continue", message=message, handoff=handoff)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _off_the_rule(code, what, raw, allowed):
|
|
238
|
+
"""Halt on a missing value, or one outside the rule's set."""
|
|
239
|
+
if raw is None:
|
|
240
|
+
return _halt(code, f"no-{what}", f"{code} reported no {what}.")
|
|
241
|
+
return _halt(code, f"unknown-{what}", f"{code} reported the {what} {raw!r}, which is not {allowed}.")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _gate_ts(result: dict) -> dict:
|
|
245
|
+
raw = _field(result, "verdict", "result")
|
|
246
|
+
verdict = TS_VERDICTS.get(_token(raw) or "")
|
|
247
|
+
if verdict is None:
|
|
248
|
+
if raw is None and _is_error(result):
|
|
249
|
+
reason = _halt_reason(result)
|
|
250
|
+
return _halt("TS", reason, f"TS halted before a verdict ({reason}).")
|
|
251
|
+
return _off_the_rule("TS", "verdict", raw, "PASS, FAIL, INCONCLUSIVE or pass-with-drift")
|
|
252
|
+
if verdict != "PASS":
|
|
253
|
+
detail = f" ({_halt_reason(result)})" if _is_error(result) else ""
|
|
254
|
+
return _halt("TS", verdict, f"TS verdict {verdict}{detail}: the skill is not exported.")
|
|
255
|
+
if _is_error(result):
|
|
256
|
+
reason = _halt_reason(result)
|
|
257
|
+
return _halt("TS", reason, f"TS reported PASS but ended in an error ({reason}): the skill is not exported.")
|
|
258
|
+
route = result.get("next_workflow", _MISSING)
|
|
259
|
+
if route not in (_MISSING, EXPORT_ROUTE):
|
|
260
|
+
message = f"TS verdict PASS, but next_workflow is {route!r}, not {EXPORT_ROUTE!r}."
|
|
261
|
+
return _halt("TS", "not-routed-to-export", message)
|
|
262
|
+
return _go("TS", "TS verdict PASS: the skill goes on to export.")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _gate_an(result: dict, minimum: int) -> dict:
|
|
266
|
+
if _is_error(result):
|
|
267
|
+
reason = _halt_reason(result)
|
|
268
|
+
return _halt("AN", reason, f"AN halted ({reason}).")
|
|
269
|
+
status = str(result.get("status", "")).strip().lower()
|
|
270
|
+
if status == "redirect":
|
|
271
|
+
name = result.get("skill_name") or "the existing skill"
|
|
272
|
+
return _halt("AN", "redirect", f"AN routed the target to an update of {name}: run US {name}.")
|
|
273
|
+
if status == "skipped":
|
|
274
|
+
why = result.get("skipped_reason") or "AN skipped the target"
|
|
275
|
+
return _halt("AN", "skipped", f"{why}: there is no brief to go on with.")
|
|
276
|
+
units = _units(result)
|
|
277
|
+
if units is None:
|
|
278
|
+
return _halt("AN", "units-unknown", "AN reported no unit count and no brief paths.")
|
|
279
|
+
if units < minimum:
|
|
280
|
+
reason = "no-skillable-units" if units == 0 else "units-below-min"
|
|
281
|
+
message = f"AN confirmed {units} skillable unit(s); the pipeline needs at least {minimum}."
|
|
282
|
+
return _halt("AN", reason, message)
|
|
283
|
+
return _go("AN", f"AN confirmed {units} skillable unit(s).")
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _gate_as(result: dict, next_code: str | None) -> dict:
|
|
287
|
+
if _is_error(result):
|
|
288
|
+
reason = _halt_reason(result)
|
|
289
|
+
return _halt("AS", reason, f"AS halted ({reason}).")
|
|
290
|
+
raw = _field(result, "drift_score", "severity")
|
|
291
|
+
severity = _token(raw)
|
|
292
|
+
if severity not in AS_SEVERITIES:
|
|
293
|
+
return _off_the_rule("AS", "severity", raw, "CLEAN, MINOR, SIGNIFICANT or CRITICAL")
|
|
294
|
+
moved = _field(result, "upstream_moved", "upstream_moved") is True
|
|
295
|
+
ref = _field(result, "upstream_ref", "upstream_ref")
|
|
296
|
+
handoff = {"target_ref": ref.strip()} if moved and isinstance(ref, str) and ref.strip() else None
|
|
297
|
+
if severity == "CRITICAL":
|
|
298
|
+
message = "AS found CRITICAL drift: review the drift report, then run US on the skill."
|
|
299
|
+
return _halt("AS", "CRITICAL", message, handoff)
|
|
300
|
+
if severity == "CLEAN" and next_code == "US":
|
|
301
|
+
if _field(result, "next_workflow", "next_workflow") == UPDATE_ROUTE:
|
|
302
|
+
where = f" to {ref.strip()}" if isinstance(ref, str) and ref.strip() else ""
|
|
303
|
+
return _go("AS", f"AS found no drift, but upstream moved{where}: updating.", handoff)
|
|
304
|
+
return _decision("AS", "skip", "CLEAN", "No drift detected: skipping update.", skip="US")
|
|
305
|
+
return _go("AS", f"AS drift severity {severity}.", handoff)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def _gate_vs(result: dict) -> dict:
|
|
309
|
+
if _is_error(result):
|
|
310
|
+
reason = _halt_reason(result)
|
|
311
|
+
return _halt("VS", reason, f"VS halted ({reason}).")
|
|
312
|
+
raw = _field(result, "overall_verdict", "overallVerdict")
|
|
313
|
+
verdict = _token(raw)
|
|
314
|
+
if verdict not in VS_VERDICTS:
|
|
315
|
+
return _off_the_rule("VS", "verdict", raw, "FEASIBLE, CONDITIONALLY_FEASIBLE or NOT_FEASIBLE")
|
|
316
|
+
raw_coverage = _field(result, "coverage_percentage", "coveragePercentage")
|
|
317
|
+
coverage = _percentage(raw_coverage)
|
|
318
|
+
if coverage is None:
|
|
319
|
+
return _off_the_rule("VS", "coverage", raw_coverage, "a percentage from 0 to 100")
|
|
320
|
+
if coverage == 0:
|
|
321
|
+
message = ("VS covered none of the architecture's technologies (coverage 0%), so it "
|
|
322
|
+
"verified nothing to go on with: create their skills, then re-run VS.")
|
|
323
|
+
return _halt("VS", "zero-coverage", message)
|
|
324
|
+
return _go("VS", f"VS verdict {verdict} at {coverage}% coverage.")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
_UNREADABLE = {
|
|
328
|
+
"no-result": "{code} left no result envelope to read.",
|
|
329
|
+
"result-unreadable": "The result given for {code} is not one JSON object, or it cannot be read as UTF-8.",
|
|
330
|
+
"wrong-result": "The result given for {code} is another workflow's envelope.",
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def gate(code: str, text: str | None, *, next_code: str | None = None, minimum: int = 1) -> dict:
|
|
335
|
+
"""Decide continue, skip or halt for `code` from the text its step produced."""
|
|
336
|
+
code = code.upper()
|
|
337
|
+
result, problem = read_result(code, text)
|
|
338
|
+
if problem:
|
|
339
|
+
return _halt(code, problem, _UNREADABLE[problem].format(code=code))
|
|
340
|
+
if code == "TS":
|
|
341
|
+
return _gate_ts(result)
|
|
342
|
+
if code == "AN":
|
|
343
|
+
return _gate_an(result, minimum)
|
|
344
|
+
if code == "AS":
|
|
345
|
+
return _gate_as(result, next_code.upper() if next_code else None)
|
|
346
|
+
return _gate_vs(result)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
# --------------------------------------------------------------------------
|
|
350
|
+
# CLI
|
|
351
|
+
# --------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
355
|
+
parser = argparse.ArgumentParser(
|
|
356
|
+
prog="pipeline-gate",
|
|
357
|
+
description=(
|
|
358
|
+
"Circuit-breaker decision for one finished pipeline step "
|
|
359
|
+
"(pipeline-mode.md step 4d): read the step's result envelope line "
|
|
360
|
+
"or result record and print continue, skip or halt with a reason."
|
|
361
|
+
),
|
|
362
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
363
|
+
)
|
|
364
|
+
parser.add_argument(
|
|
365
|
+
"--code",
|
|
366
|
+
required=True,
|
|
367
|
+
type=str.upper,
|
|
368
|
+
choices=GATED_CODES,
|
|
369
|
+
help="The code of the step that finished.",
|
|
370
|
+
)
|
|
371
|
+
parser.add_argument(
|
|
372
|
+
"--next",
|
|
373
|
+
type=str.upper,
|
|
374
|
+
help="The code of the next workflow in the plan, if any (a CLEAN AS skips a next US, unless it "
|
|
375
|
+
"routes to update-skill).",
|
|
376
|
+
)
|
|
377
|
+
parser.add_argument(
|
|
378
|
+
"--min",
|
|
379
|
+
type=int,
|
|
380
|
+
default=1,
|
|
381
|
+
help="The fewest units AN must confirm (AN's [min:N]; default 1). Read for AN only.",
|
|
382
|
+
)
|
|
383
|
+
parser.add_argument(
|
|
384
|
+
"--result",
|
|
385
|
+
help=(
|
|
386
|
+
"A file holding the envelope line or a result record, for tests and CI "
|
|
387
|
+
"(stdin when omitted or '-'). Pipeline Mode gates the envelope line the "
|
|
388
|
+
"step just printed: a -latest.json record may be an earlier run's."
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
return parser
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _force_utf8(stream) -> None:
|
|
395
|
+
"""Reconfigure the JSON output to UTF-8 (a Windows console uses cp1252)."""
|
|
396
|
+
if hasattr(stream, "reconfigure"):
|
|
397
|
+
errors = getattr(stream, "errors", None)
|
|
398
|
+
if errors is None:
|
|
399
|
+
stream.reconfigure(encoding="utf-8")
|
|
400
|
+
else:
|
|
401
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _read_input(path: str | None) -> str | None:
|
|
405
|
+
"""The text to gate, or None when it cannot be read as UTF-8.
|
|
406
|
+
|
|
407
|
+
A byte-order mark is dropped: a Windows editor or shell may write one.
|
|
408
|
+
"""
|
|
409
|
+
try:
|
|
410
|
+
if path in (None, "-"):
|
|
411
|
+
if hasattr(sys.stdin, "reconfigure"):
|
|
412
|
+
sys.stdin.reconfigure(encoding="utf-8-sig", errors="strict")
|
|
413
|
+
return sys.stdin.read()
|
|
414
|
+
return Path(path).read_text(encoding="utf-8-sig")
|
|
415
|
+
except (OSError, UnicodeDecodeError):
|
|
416
|
+
return None
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def main(argv: list[str] | None = None) -> int:
|
|
420
|
+
_force_utf8(sys.stdout)
|
|
421
|
+
args = _build_parser().parse_args(argv)
|
|
422
|
+
decision = gate(args.code, _read_input(args.result), next_code=args.next, minimum=args.min)
|
|
423
|
+
print(json.dumps(decision, indent=2))
|
|
424
|
+
return 0
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
if __name__ == "__main__":
|
|
428
|
+
sys.exit(main())
|