bmad-module-skill-forge 2.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +143 -18
- package/docs/verifying-a-skill.md +87 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +3 -3
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +31 -59
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +233 -64
- package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +772 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +21 -3
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +197 -55
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +5247 -96
- package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +664 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +469 -205
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +700 -0
- package/src/shared/scripts/skf-hash-content.py +180 -20
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +161 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +143 -20
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +176 -28
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +155 -14
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +787 -121
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +120 -67
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +23 -2
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +3945 -152
- package/src/shared/scripts/skf-write-skill-brief.py +406 -25
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +28 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/health-check.md +2 -0
- package/src/skf-audit-skill/references/init.md +149 -210
- package/src/skf-audit-skill/references/re-index.md +103 -110
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +34 -25
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +80 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
- package/src/skf-brief-skill/SKILL.md +37 -26
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +18 -25
- package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +144 -127
- package/src/skf-brief-skill/references/confirm-brief.md +11 -9
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +7 -10
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
- package/src/skf-create-skill/assets/skill-sections.md +21 -11
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +119 -70
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
- package/src/skf-create-skill/references/extract.md +204 -162
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
- package/src/skf-create-skill/references/extraction-patterns.md +588 -201
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +37 -57
- package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +83 -146
- package/src/skf-drop-skill/references/health-check.md +2 -0
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +122 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +209 -264
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +164 -91
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +164 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +42 -155
- package/src/skf-test-skill/references/source-access-protocol.md +18 -94
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +380 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
- package/src/skf-test-skill/scripts/score-signatures.py +479 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +253 -223
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +254 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +182 -49
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +102 -35
- package/src/skf-update-skill/references/write.md +357 -155
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# /// script
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
|
+
# dependencies = []
|
|
5
|
+
# ///
|
|
6
|
+
"""SKF test-skill result context: the emitter payload of a finished run.
|
|
7
|
+
|
|
8
|
+
report.md §4c hands the shared emitter (`skf-emit-result-envelope.py emit
|
|
9
|
+
--workflow skf-test-skill`) one payload, which the emitter turns into the
|
|
10
|
+
SKF_TEST_RESULT_JSON line and the run's result files. Every value in it
|
|
11
|
+
follows from records the run already wrote, so this script builds it rather
|
|
12
|
+
than the model retyping them from memory:
|
|
13
|
+
|
|
14
|
+
- the report frontmatter: skillName, runId, testMode, hardGate,
|
|
15
|
+
testResult, workspaceDrift, score, threshold, thresholdFallback,
|
|
16
|
+
originalThreshold and evidenceReportPath (the steps write the verdict
|
|
17
|
+
inputs there, so a compacted context cannot change them);
|
|
18
|
+
- the gap ledger: the gap counts by severity (gap-ledger.py's summary);
|
|
19
|
+
- the scoring script's output, which score.md saves: the verdict, score
|
|
20
|
+
and threshold it settled, activeCategories and inconclusiveReasons. A
|
|
21
|
+
run the hard gate blocked was never scored and has none.
|
|
22
|
+
|
|
23
|
+
A scored run's verdict fields are checked against the scoring output
|
|
24
|
+
(`scored_fields`): testResult must be the verdict the output settled,
|
|
25
|
+
`effectiveResult` when the output has one, else `result`, in lower case,
|
|
26
|
+
with `pass` read as `pass-with-drift` when the frontmatter workspaceDrift
|
|
27
|
+
is `overridden`; score its `totalScore`; threshold the one the verdict was
|
|
28
|
+
settled against (compute-score.py's FALLBACK_FLOOR after a threshold
|
|
29
|
+
fallback, else its `threshold`); and thresholdFallback and
|
|
30
|
+
originalThreshold its override group's. A field copied from anything else
|
|
31
|
+
(`result` over a cap's FAIL, a bare pass under drift, the target threshold
|
|
32
|
+
of a run the fallback accepted) exits 1 and writes no payload, so
|
|
33
|
+
report.md halts before it publishes the report or writes a result file.
|
|
34
|
+
|
|
35
|
+
The derived fields live here, in one place:
|
|
36
|
+
|
|
37
|
+
testResult verdict summary.result exit_code next_workflow
|
|
38
|
+
pass PASS PASS 0 export-skill
|
|
39
|
+
pass-with-drift pass-with-drift PASS_WITH_DRIFT 4 update-skill
|
|
40
|
+
fail FAIL FAIL 2 update-skill
|
|
41
|
+
inconclusive INCONCLUSIVE INCONCLUSIVE 3 null
|
|
42
|
+
|
|
43
|
+
A run the hard gate blocked (frontmatter hardGate: 'blocked') takes the
|
|
44
|
+
hard gate's own payload (hard-gate.py `blocked_envelope`): status error,
|
|
45
|
+
halt_reason hard-gate-blocked, verdict FAIL, exit 2, score and threshold
|
|
46
|
+
null. threshold_fallback and original_threshold appear only when the
|
|
47
|
+
threshold fallback fired: absent, never false or null, otherwise.
|
|
48
|
+
|
|
49
|
+
Payload (written to --output, UTF-8 JSON):
|
|
50
|
+
{<the envelope fields: status, skill_name, verdict, score, threshold,
|
|
51
|
+
report_path, next_workflow, exit_code, halt_reason, and
|
|
52
|
+
threshold_fallback and original_threshold when the fallback fired>,
|
|
53
|
+
"warnings": [<each --warning, in order>] (only when one is given),
|
|
54
|
+
"result_contract": {"skill": "skf-test-skill", <the envelope fields>,
|
|
55
|
+
"outputs": [{"type": "report", "path": <report>}],
|
|
56
|
+
"summary": {"score", "threshold", "result", "testMode",
|
|
57
|
+
"activeCategories", "inconclusiveReasons" (INCONCLUSIVE),
|
|
58
|
+
"threshold_fallback", "original_threshold",
|
|
59
|
+
"evidence_report_path" (fallback only), "gapCounts",
|
|
60
|
+
"hardGate"},
|
|
61
|
+
"runId": <runId>, "healthCheckDispatched": <bool>}}
|
|
62
|
+
|
|
63
|
+
CLI:
|
|
64
|
+
uv run build-result-context.py --report <report.md> [--report-path <path>]
|
|
65
|
+
--ledger <ledger.json> [--score <scoring output>] [--no-health-check]
|
|
66
|
+
[--warning <text>]... --output <result-context.json>
|
|
67
|
+
|
|
68
|
+
--score is required unless the frontmatter says the hard gate blocked the
|
|
69
|
+
run. --report-path is the path the payload names the report by (default:
|
|
70
|
+
--report): report.md builds the payload from the report before it gives
|
|
71
|
+
the report its public name, and passes that name here.
|
|
72
|
+
|
|
73
|
+
--warning hands the emitter a warning the run kept for its result (such as
|
|
74
|
+
`customization_resolver_unavailable: <reason>`); the emitter adds the ones
|
|
75
|
+
the run recorded in its sink, and stamps all of them into the envelope and
|
|
76
|
+
the result files.
|
|
77
|
+
|
|
78
|
+
stdout (one object): {"status", "verdict", "exit_code", "next_workflow",
|
|
79
|
+
"halt_reason", "target", "output"}; `target` is the --target the emit call
|
|
80
|
+
takes: "stderr" for a blocked run, else "stdout".
|
|
81
|
+
|
|
82
|
+
Exit codes:
|
|
83
|
+
0 the payload was written
|
|
84
|
+
1 an input is missing or unreadable, the report's frontmatter holds no
|
|
85
|
+
settled verdict or a verdict field the scoring output does not settle,
|
|
86
|
+
or --output cannot be written ({"status": "error", "error"} on stdout)
|
|
87
|
+
2 a usage error: no --score for a run the hard gate did not block (the
|
|
88
|
+
same JSON on stdout), or a flag argparse refuses (its usage on stderr)
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
from __future__ import annotations
|
|
92
|
+
|
|
93
|
+
import argparse
|
|
94
|
+
import importlib.util
|
|
95
|
+
import json
|
|
96
|
+
import re
|
|
97
|
+
import sys
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
|
|
100
|
+
WORKFLOW = "skf-test-skill"
|
|
101
|
+
# testResult -> (verdict, summary.result, exit_code, next_workflow)
|
|
102
|
+
VERDICTS = {
|
|
103
|
+
"pass": ("PASS", "PASS", 0, "export-skill"),
|
|
104
|
+
"pass-with-drift": ("pass-with-drift", "PASS_WITH_DRIFT", 4, "update-skill"),
|
|
105
|
+
"fail": ("FAIL", "FAIL", 2, "update-skill"),
|
|
106
|
+
"inconclusive": ("INCONCLUSIVE", "INCONCLUSIVE", 3, None),
|
|
107
|
+
}
|
|
108
|
+
_KEY_RE = re.compile(r"^([A-Za-z_][\w-]*):\s*(.*?)\s*$")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ContextError(Exception):
|
|
112
|
+
"""An input the payload cannot be built from: exit 1."""
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class UsageError(ContextError):
|
|
116
|
+
"""A command the caller can correct and run again: exit 2."""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _sibling(name: str, module: str):
|
|
120
|
+
"""Import a script that sits beside this one (gap-ledger.py, hard-gate.py)."""
|
|
121
|
+
path = Path(__file__).resolve().parent / name
|
|
122
|
+
spec = importlib.util.spec_from_file_location(module, path)
|
|
123
|
+
if spec is None or spec.loader is None or not path.is_file():
|
|
124
|
+
raise ContextError(f"{name} not found beside {Path(__file__).name}")
|
|
125
|
+
loaded = importlib.util.module_from_spec(spec)
|
|
126
|
+
spec.loader.exec_module(loaded)
|
|
127
|
+
return loaded
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def read_frontmatter(text: str) -> dict[str, str]:
|
|
131
|
+
"""Top-level scalar keys of the leading `---` block, quotes removed.
|
|
132
|
+
|
|
133
|
+
>>> read_frontmatter("---\\ntestResult: 'pass'\\nscore: 91.5%\\nsteps: ['a']\\n---\\n# R")
|
|
134
|
+
{'testResult': 'pass', 'score': '91.5%', 'steps': "['a']"}
|
|
135
|
+
>>> read_frontmatter("# no frontmatter")
|
|
136
|
+
{}
|
|
137
|
+
"""
|
|
138
|
+
lines = text.lstrip("").split("\n")
|
|
139
|
+
if not lines or lines[0].strip() != "---":
|
|
140
|
+
return {}
|
|
141
|
+
out: dict[str, str] = {}
|
|
142
|
+
for line in lines[1:]:
|
|
143
|
+
if line.strip() == "---":
|
|
144
|
+
return out
|
|
145
|
+
m = _KEY_RE.match(line.rstrip("\r"))
|
|
146
|
+
if not m:
|
|
147
|
+
continue
|
|
148
|
+
value = m.group(2)
|
|
149
|
+
if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
|
|
150
|
+
value = value[1:-1]
|
|
151
|
+
out[m.group(1)] = value
|
|
152
|
+
return {}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def percent(value: str | None):
|
|
156
|
+
"""'87.5%' -> 87.5, '90%' or '90' -> 90, '' or None -> None.
|
|
157
|
+
|
|
158
|
+
>>> percent("87.5%"), percent("90"), percent(""), percent(None)
|
|
159
|
+
(87.5, 90, None, None)
|
|
160
|
+
"""
|
|
161
|
+
text = (value or "").strip().rstrip("%").strip()
|
|
162
|
+
if not text:
|
|
163
|
+
return None
|
|
164
|
+
try:
|
|
165
|
+
number = float(text)
|
|
166
|
+
except ValueError:
|
|
167
|
+
raise ContextError(f"not a percentage: {value!r}") from None
|
|
168
|
+
return int(number) if number.is_integer() else number
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _read_json(path: str, label: str):
|
|
172
|
+
try:
|
|
173
|
+
return json.loads(Path(path).read_bytes().decode("utf-8-sig"))
|
|
174
|
+
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
175
|
+
raise ContextError(f"{label} {path} cannot be read: {exc}") from exc
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _fallback_floor() -> int:
|
|
179
|
+
"""The threshold a fallback accepts a run at: compute-score.py's, which applies the fallback."""
|
|
180
|
+
return _sibling("compute-score.py", "skf_compute_score_floor").FALLBACK_FLOOR
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def settled_test_result(scored: dict, workspace_drift: str | None) -> str | None:
|
|
184
|
+
"""The testResult the scoring output settles, or None when it holds no verdict.
|
|
185
|
+
|
|
186
|
+
>>> settled_test_result({"result": "PASS"}, "ok")
|
|
187
|
+
'pass'
|
|
188
|
+
>>> settled_test_result({"result": "PASS", "effectiveResult": "FAIL", "capReason": "Cap 1"}, "ok")
|
|
189
|
+
'fail'
|
|
190
|
+
>>> settled_test_result({"result": "FAIL", "effectiveResult": "PASS"}, "overridden")
|
|
191
|
+
'pass-with-drift'
|
|
192
|
+
>>> settled_test_result({"result": "INCONCLUSIVE"}, "overridden")
|
|
193
|
+
'inconclusive'
|
|
194
|
+
>>> settled_test_result({"error": "bad flag", "code": "INVALID_INPUT"}, "ok") is None
|
|
195
|
+
True
|
|
196
|
+
"""
|
|
197
|
+
verdict = scored.get("effectiveResult") or scored.get("result")
|
|
198
|
+
settled = verdict.lower() if isinstance(verdict, str) else None
|
|
199
|
+
if settled not in ("pass", "fail", "inconclusive"):
|
|
200
|
+
return None
|
|
201
|
+
return "pass-with-drift" if settled == "pass" and workspace_drift == "overridden" else settled
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def scored_fields(scored: dict, workspace_drift: str | None) -> dict | None:
|
|
205
|
+
"""The frontmatter verdict fields the scoring output settles, or None when it holds no verdict.
|
|
206
|
+
|
|
207
|
+
>>> scored_fields({"result": "PASS", "totalScore": 91.5, "threshold": 80}, "ok")
|
|
208
|
+
{'testResult': 'pass', 'score': 91.5, 'threshold': 80, 'thresholdFallback': False, 'originalThreshold': None}
|
|
209
|
+
>>> fell_back = {"result": "FAIL", "effectiveResult": "PASS", "capReason": None, "thresholdFallback": True,
|
|
210
|
+
... "originalThreshold": 90, "totalScore": 85, "threshold": 90}
|
|
211
|
+
>>> scored_fields(fell_back, "ok")
|
|
212
|
+
{'testResult': 'pass', 'score': 85, 'threshold': 80, 'thresholdFallback': True, 'originalThreshold': 90}
|
|
213
|
+
"""
|
|
214
|
+
test_result = settled_test_result(scored, workspace_drift)
|
|
215
|
+
if test_result is None:
|
|
216
|
+
return None
|
|
217
|
+
fallback = scored.get("thresholdFallback") is True
|
|
218
|
+
return {
|
|
219
|
+
"testResult": test_result,
|
|
220
|
+
"score": scored.get("totalScore"),
|
|
221
|
+
"threshold": _fallback_floor() if fallback else scored.get("threshold"),
|
|
222
|
+
"thresholdFallback": fallback,
|
|
223
|
+
"originalThreshold": scored.get("originalThreshold") if fallback else None,
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def build(report: str, ledger: str, score: str | None = None, health_check: bool = True,
|
|
228
|
+
warnings: list[str] | None = None, report_path: str | None = None) -> dict:
|
|
229
|
+
"""The emitter payload for the run whose report and ledger are given; `report_path`
|
|
230
|
+
(default `report`) is the path the payload names the report by."""
|
|
231
|
+
report_path = report_path or report
|
|
232
|
+
try:
|
|
233
|
+
front = read_frontmatter(Path(report).read_bytes().decode("utf-8-sig"))
|
|
234
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
235
|
+
raise ContextError(f"--report {report} cannot be read: {exc}") from exc
|
|
236
|
+
if not front:
|
|
237
|
+
raise ContextError(f"--report {report} has no frontmatter")
|
|
238
|
+
gl = _sibling("gap-ledger.py", "skf_gap_ledger")
|
|
239
|
+
try:
|
|
240
|
+
counts = gl.summarize(Path(ledger), gl.load_ledger(Path(ledger)))["counts"]
|
|
241
|
+
except gl.LedgerError as exc:
|
|
242
|
+
raise ContextError(f"--ledger {ledger}: {exc}") from exc
|
|
243
|
+
|
|
244
|
+
skill = front.get("skillName") or None
|
|
245
|
+
blocked = front.get("hardGate") == "blocked"
|
|
246
|
+
if blocked:
|
|
247
|
+
envelope = dict(_sibling("hard-gate.py", "skf_hard_gate").blocked_envelope(skill, report_path))
|
|
248
|
+
result, active, inconclusive = "FAIL", [], None
|
|
249
|
+
else:
|
|
250
|
+
test_result = front.get("testResult", "")
|
|
251
|
+
if test_result not in VERDICTS:
|
|
252
|
+
raise ContextError(f"--report {report} holds no settled verdict (testResult {test_result!r})")
|
|
253
|
+
verdict, result, exit_code, next_workflow = VERDICTS[test_result]
|
|
254
|
+
envelope = {
|
|
255
|
+
"status": "success",
|
|
256
|
+
"skill_name": skill,
|
|
257
|
+
"verdict": verdict,
|
|
258
|
+
"score": percent(front.get("score")),
|
|
259
|
+
"threshold": percent(front.get("threshold")),
|
|
260
|
+
"report_path": report_path,
|
|
261
|
+
"next_workflow": next_workflow,
|
|
262
|
+
"exit_code": exit_code,
|
|
263
|
+
"halt_reason": None,
|
|
264
|
+
}
|
|
265
|
+
if not score:
|
|
266
|
+
raise UsageError("--score is required for a run the hard gate did not block: its verdict "
|
|
267
|
+
"fields are checked against the scoring output")
|
|
268
|
+
scored = _read_json(score, "--score")
|
|
269
|
+
if not isinstance(scored, dict):
|
|
270
|
+
raise ContextError(f"--score {score} is not a JSON object")
|
|
271
|
+
settled = scored_fields(scored, front.get("workspaceDrift"))
|
|
272
|
+
if settled is None:
|
|
273
|
+
raise ContextError(f"--score {score} holds no verdict (no effectiveResult or result)")
|
|
274
|
+
fell_back = front.get("thresholdFallback", "").lower() == "true"
|
|
275
|
+
written = {"testResult": test_result, "score": envelope["score"], "threshold": envelope["threshold"],
|
|
276
|
+
"thresholdFallback": fell_back,
|
|
277
|
+
"originalThreshold": percent(front.get("originalThreshold")) if fell_back else None}
|
|
278
|
+
wrong = [f"{key} {written[key]!r} where it settles {settled[key]!r}"
|
|
279
|
+
for key in settled if written[key] != settled[key]]
|
|
280
|
+
if wrong:
|
|
281
|
+
raise ContextError(f"--report {report} disagrees with the scoring output {score}: "
|
|
282
|
+
+ "; ".join(wrong))
|
|
283
|
+
active = scored.get("activeCategories", [])
|
|
284
|
+
inconclusive = scored.get("inconclusiveReasons") if verdict == "INCONCLUSIVE" else None
|
|
285
|
+
|
|
286
|
+
fallback = front.get("thresholdFallback", "").lower() == "true" and not blocked
|
|
287
|
+
if fallback:
|
|
288
|
+
envelope["threshold_fallback"] = True
|
|
289
|
+
envelope["original_threshold"] = percent(front.get("originalThreshold"))
|
|
290
|
+
|
|
291
|
+
summary = {
|
|
292
|
+
"score": envelope["score"],
|
|
293
|
+
"threshold": envelope["threshold"],
|
|
294
|
+
"result": result,
|
|
295
|
+
"testMode": front.get("testMode") or None,
|
|
296
|
+
"activeCategories": active,
|
|
297
|
+
}
|
|
298
|
+
if inconclusive is not None:
|
|
299
|
+
summary["inconclusiveReasons"] = inconclusive
|
|
300
|
+
if fallback:
|
|
301
|
+
summary["threshold_fallback"] = True
|
|
302
|
+
summary["original_threshold"] = envelope["original_threshold"]
|
|
303
|
+
summary["evidence_report_path"] = front.get("evidenceReportPath") or None
|
|
304
|
+
summary["gapCounts"] = counts
|
|
305
|
+
summary["hardGate"] = front.get("hardGate") or None
|
|
306
|
+
|
|
307
|
+
contract = {"skill": WORKFLOW, **envelope,
|
|
308
|
+
"outputs": [{"type": "report", "path": report_path}],
|
|
309
|
+
"summary": summary,
|
|
310
|
+
"runId": front.get("runId") or None,
|
|
311
|
+
"healthCheckDispatched": health_check}
|
|
312
|
+
payload = {**envelope, "result_contract": contract}
|
|
313
|
+
kept = [w.strip() for w in warnings or [] if w and w.strip()]
|
|
314
|
+
if kept:
|
|
315
|
+
payload["warnings"] = kept
|
|
316
|
+
return payload
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
320
|
+
parser = argparse.ArgumentParser(
|
|
321
|
+
prog="build-result-context",
|
|
322
|
+
description=("Build the shared emitter's payload for a finished test-skill run from its "
|
|
323
|
+
"report frontmatter, its gap ledger and the scoring output."),
|
|
324
|
+
)
|
|
325
|
+
parser.add_argument("--report", required=True, help="the test report to read")
|
|
326
|
+
parser.add_argument("--report-path",
|
|
327
|
+
help="the path the payload names the report by: its public name (default: --report)")
|
|
328
|
+
parser.add_argument("--ledger", required=True, help="the run's gap ledger")
|
|
329
|
+
parser.add_argument("--score", help="the scoring script's output (required unless the hard gate blocked "
|
|
330
|
+
"the run)")
|
|
331
|
+
parser.add_argument("--no-health-check", action="store_true",
|
|
332
|
+
help="the run was given --no-health-check (healthCheckDispatched false)")
|
|
333
|
+
parser.add_argument("--warning", action="append", default=[],
|
|
334
|
+
help="a warning the run kept for its result (repeatable)")
|
|
335
|
+
parser.add_argument("--output", required=True, help="where to write the payload")
|
|
336
|
+
return parser
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def main(argv: list[str] | None = None) -> int:
|
|
340
|
+
args = _build_parser().parse_args(argv)
|
|
341
|
+
try:
|
|
342
|
+
payload = build(args.report, args.ledger, args.score, not args.no_health_check, args.warning,
|
|
343
|
+
args.report_path)
|
|
344
|
+
out = Path(args.output)
|
|
345
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
346
|
+
out.write_bytes((json.dumps(payload, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
|
|
347
|
+
except (ContextError, OSError) as exc:
|
|
348
|
+
print(json.dumps({"status": "error", "error": str(exc)}))
|
|
349
|
+
return 2 if isinstance(exc, UsageError) else 1
|
|
350
|
+
blocked = payload["halt_reason"] is not None
|
|
351
|
+
print(json.dumps({
|
|
352
|
+
"status": payload["status"],
|
|
353
|
+
"verdict": payload["verdict"],
|
|
354
|
+
"exit_code": payload["exit_code"],
|
|
355
|
+
"next_workflow": payload["next_workflow"],
|
|
356
|
+
"halt_reason": payload["halt_reason"],
|
|
357
|
+
"target": "stderr" if blocked else "stdout",
|
|
358
|
+
"output": out.as_posix(),
|
|
359
|
+
}, indent=2))
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _force_utf8(*streams) -> None:
|
|
364
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
365
|
+
|
|
366
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
367
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
368
|
+
"""
|
|
369
|
+
for stream in streams:
|
|
370
|
+
if hasattr(stream, "reconfigure"):
|
|
371
|
+
errors = getattr(stream, "errors", None)
|
|
372
|
+
if errors is None:
|
|
373
|
+
stream.reconfigure(encoding="utf-8")
|
|
374
|
+
else:
|
|
375
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
if __name__ == "__main__":
|
|
379
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
380
|
+
sys.exit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
5
|
"""Deterministic metadata export-count coherence cross-check.
|
|
6
6
|
|
|
@@ -39,10 +39,15 @@ Stack skills (`skill_type == "stack"`) and reference apps
|
|
|
39
39
|
intentionally *different* surfaces, so comparing them yields only false drift.
|
|
40
40
|
|
|
41
41
|
CLI usage:
|
|
42
|
+
uv run check-metadata-coherence.py --inputs <coverage-inputs.json> # a file
|
|
42
43
|
uv run check-metadata-coherence.py '<JSON>' # positional
|
|
43
44
|
uv run check-metadata-coherence.py --json-input '<JSON>' # explicit flag
|
|
44
45
|
cat input.json | uv run check-metadata-coherence.py --stdin # piped
|
|
45
46
|
|
|
47
|
+
--inputs reads the input object from a file: load-coverage-inputs.py
|
|
48
|
+
metadata writes these keys from metadata.json and the provenance map (any
|
|
49
|
+
other key in the file is ignored), so no count is copied by hand.
|
|
50
|
+
|
|
46
51
|
Input schema (one object; omit any count that is absent for the skill):
|
|
47
52
|
{
|
|
48
53
|
"skillType": "stack" | ... | null, # metadata.json.skill_type
|
|
@@ -83,11 +88,12 @@ from __future__ import annotations
|
|
|
83
88
|
import argparse
|
|
84
89
|
import json
|
|
85
90
|
import sys
|
|
91
|
+
from pathlib import Path
|
|
86
92
|
|
|
87
93
|
DEFAULT_DRIFT_PCT = 10
|
|
88
94
|
CATEGORY = "structural/metadata coherence"
|
|
89
95
|
|
|
90
|
-
# Stable source labels used in finding bodies
|
|
96
|
+
# Stable source labels used in finding bodies.
|
|
91
97
|
LABEL_EXPORTS_PUBLIC_API = "stats.exports_public_api"
|
|
92
98
|
LABEL_EXPORTS_LENGTH = "exports[].length"
|
|
93
99
|
LABEL_EXPORTS_DOCUMENTED = "stats.exports_documented"
|
|
@@ -317,12 +323,19 @@ def _build_parser():
|
|
|
317
323
|
action="store_true",
|
|
318
324
|
help="Read the JSON object from stdin.",
|
|
319
325
|
)
|
|
326
|
+
src.add_argument(
|
|
327
|
+
"--inputs",
|
|
328
|
+
metavar="PATH",
|
|
329
|
+
help="Read the JSON object from this file (load-coverage-inputs.py metadata output).",
|
|
330
|
+
)
|
|
320
331
|
return parser
|
|
321
332
|
|
|
322
333
|
|
|
323
334
|
def _resolve_input(args):
|
|
324
335
|
if args.stdin:
|
|
325
336
|
return sys.stdin.read()
|
|
337
|
+
if args.inputs is not None:
|
|
338
|
+
return Path(args.inputs).read_bytes().decode("utf-8-sig")
|
|
326
339
|
if args.json_input_flag is not None:
|
|
327
340
|
return args.json_input_flag
|
|
328
341
|
if args.json_input is not None:
|
|
@@ -333,11 +346,15 @@ def _resolve_input(args):
|
|
|
333
346
|
def main(argv=None):
|
|
334
347
|
parser = _build_parser()
|
|
335
348
|
args = parser.parse_args(argv)
|
|
336
|
-
|
|
349
|
+
try:
|
|
350
|
+
raw = _resolve_input(args)
|
|
351
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
352
|
+
print(f"error: cannot read --inputs {args.inputs}: {exc}", file=sys.stderr)
|
|
353
|
+
return 1
|
|
337
354
|
if not raw.strip():
|
|
338
355
|
parser.print_usage(file=sys.stderr)
|
|
339
356
|
print(
|
|
340
|
-
"error: no input provided (positional arg, --json-input, or --
|
|
357
|
+
"error: no input provided (positional arg, --json-input, --stdin or --inputs)",
|
|
341
358
|
file=sys.stderr,
|
|
342
359
|
)
|
|
343
360
|
return 1
|
|
@@ -355,5 +372,21 @@ def main(argv=None):
|
|
|
355
372
|
return 0
|
|
356
373
|
|
|
357
374
|
|
|
375
|
+
def _force_utf8(*streams) -> None:
|
|
376
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
377
|
+
|
|
378
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
379
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
380
|
+
"""
|
|
381
|
+
for stream in streams:
|
|
382
|
+
if hasattr(stream, "reconfigure"):
|
|
383
|
+
errors = getattr(stream, "errors", None)
|
|
384
|
+
if errors is None:
|
|
385
|
+
stream.reconfigure(encoding="utf-8")
|
|
386
|
+
else:
|
|
387
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
388
|
+
|
|
389
|
+
|
|
358
390
|
if __name__ == "__main__":
|
|
391
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
359
392
|
raise SystemExit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# dependencies = []
|
|
5
5
|
# ///
|
|
6
6
|
"""Deterministic combined external-validation score.
|
|
@@ -13,12 +13,12 @@ run-to-run (an odd-sum average such as (80 + 73) / 2 = 76.5 is exactly where an
|
|
|
13
13
|
in-prompt round swings).
|
|
14
14
|
|
|
15
15
|
Rule (external-validators.md §4):
|
|
16
|
-
* both tools ran -> mean of skill-check
|
|
16
|
+
* both tools ran -> mean of the skill-check and Tessl Review scores
|
|
17
17
|
* one tool ran -> that tool's score
|
|
18
18
|
* neither ran -> null (scoring step redistributes the external weight)
|
|
19
19
|
|
|
20
|
-
Both scores are on the same 0-100 scale (skill-check quality score;
|
|
21
|
-
|
|
20
|
+
Both scores are on the same 0-100 scale (skill-check's quality score; the Tessl
|
|
21
|
+
Review score, `review_score` from skf-tessl-review.py, when the user opted in). Rounding matches compute-score.py (JS-compatible half-up) so the
|
|
22
22
|
number this script emits and the one compute-score.py weights agree to the digit.
|
|
23
23
|
|
|
24
24
|
Input schema (one JSON object; a tool that did not run is null or omitted):
|
|
@@ -40,6 +40,10 @@ CLI usage (mirrors compute-score.py):
|
|
|
40
40
|
uv run combine-external-scores.py --json-input '<JSON>' # explicit flag
|
|
41
41
|
cat input.json | uv run combine-external-scores.py --stdin # piped input
|
|
42
42
|
|
|
43
|
+
--output <file> also writes the result to a file (UTF-8 JSON), the one
|
|
44
|
+
compute-score.py --external reads; a refused input removes a file left
|
|
45
|
+
there earlier.
|
|
46
|
+
|
|
43
47
|
Exit codes:
|
|
44
48
|
0 — score emitted successfully
|
|
45
49
|
1 — no input / input could not be parsed as JSON
|
|
@@ -52,6 +56,7 @@ import argparse
|
|
|
52
56
|
import json
|
|
53
57
|
import math
|
|
54
58
|
import sys
|
|
59
|
+
from pathlib import Path
|
|
55
60
|
|
|
56
61
|
SKILL_CHECK = "skill-check"
|
|
57
62
|
TESSL = "tessl"
|
|
@@ -121,8 +126,8 @@ def _build_parser():
|
|
|
121
126
|
prog="combine-external-scores",
|
|
122
127
|
description=(
|
|
123
128
|
"Deterministic combined external-validation score "
|
|
124
|
-
"(external-validators.md §4). Averages the skill-check and
|
|
125
|
-
"
|
|
129
|
+
"(external-validators.md §4). Averages the skill-check and Tessl "
|
|
130
|
+
"Review scores (or passes a single available score through) into the "
|
|
126
131
|
"`externalValidation` scoring input."
|
|
127
132
|
),
|
|
128
133
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
@@ -148,9 +153,19 @@ def _build_parser():
|
|
|
148
153
|
action="store_true",
|
|
149
154
|
help="Read the JSON object from stdin.",
|
|
150
155
|
)
|
|
156
|
+
parser.add_argument("--output", metavar="PATH", help="also write the result to this file")
|
|
151
157
|
return parser
|
|
152
158
|
|
|
153
159
|
|
|
160
|
+
def _write_output(path, result):
|
|
161
|
+
target = Path(path)
|
|
162
|
+
if result.get("code") == "INVALID_INPUT":
|
|
163
|
+
target.unlink(missing_ok=True)
|
|
164
|
+
return
|
|
165
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
166
|
+
target.write_bytes((json.dumps(result, indent=2) + "\n").encode("utf-8"))
|
|
167
|
+
|
|
168
|
+
|
|
154
169
|
def _resolve_input(args):
|
|
155
170
|
if args.stdin:
|
|
156
171
|
return sys.stdin.read()
|
|
@@ -180,6 +195,8 @@ def main(argv=None):
|
|
|
180
195
|
return 1
|
|
181
196
|
|
|
182
197
|
result = combine(data)
|
|
198
|
+
if args.output:
|
|
199
|
+
_write_output(args.output, result)
|
|
183
200
|
print(json.dumps(result, indent=2))
|
|
184
201
|
if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
|
|
185
202
|
return 2
|