bmad-module-skill-forge 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +51 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +14 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +215 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +17 -6
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -645
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# /// script
|
|
3
|
-
# requires-python = ">=3.
|
|
3
|
+
# requires-python = ">=3.11"
|
|
4
4
|
# ///
|
|
5
5
|
"""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
|
|
@@ -1,19 +1,31 @@
|
|
|
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 Completeness Score Calculator.
|
|
7
7
|
|
|
8
8
|
Pure-function scoring script for the SKF test-skill workflow (step-05).
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
It is the one home of the scoring rules: the weight tables, the skip
|
|
10
|
+
conditions and the proportional redistribution, the State 2 deduction, the
|
|
11
|
+
minimum-evidence floor, both caps and the threshold fallback. scoring-rules.md
|
|
12
|
+
points here and keeps only the Gap Severity table.
|
|
11
13
|
|
|
12
14
|
CLI usage:
|
|
15
|
+
uv run compute-score.py --json-input '<JSON>' [score files] # explicit flag form
|
|
13
16
|
uv run compute-score.py '<JSON>' # JSON literal as positional arg
|
|
14
|
-
uv run compute-score.py --json-input '<JSON>' # explicit flag form
|
|
15
17
|
cat input.json | uv run compute-score.py --stdin # piped input
|
|
16
18
|
|
|
19
|
+
Score files. The category scores come from the scripts that computed them,
|
|
20
|
+
read from the run folder by path, so no score is copied out of the report:
|
|
21
|
+
--coverage reconcile-coverage.py --output: exportCoverage
|
|
22
|
+
--signatures score-signatures.py score --output: signatureAccuracy, typeCoverage
|
|
23
|
+
--coherence aggregate-coherence.py --output: combinedCoherence -> coherence
|
|
24
|
+
--external combine-external-scores.py --output: externalScore -> externalValidation
|
|
25
|
+
--surface load-coverage-inputs.py surface --output: its `state2` counts
|
|
26
|
+
Each file sets its fields and wins over the same field in the JSON; a file
|
|
27
|
+
that holds an error envelope or lacks its field is refused (exit 2).
|
|
28
|
+
|
|
17
29
|
Input schema (one object):
|
|
18
30
|
{
|
|
19
31
|
"mode": "contextual" | "naive",
|
|
@@ -27,11 +39,30 @@ Input schema (one object):
|
|
|
27
39
|
},
|
|
28
40
|
"threshold": <0-100, optional, default 80>,
|
|
29
41
|
"evidenceCount": <int, optional, used for INCONCLUSIVE floor>,
|
|
30
|
-
"analysisConfidence": "<optional
|
|
31
|
-
|
|
42
|
+
"analysisConfidence": "<optional: the source access, one of ANALYSIS_CONFIDENCE;
|
|
43
|
+
metadata-only and remote-only skip Signature Accuracy
|
|
44
|
+
and Type Coverage, as Quick tier does>",
|
|
45
|
+
"toolingStatus": "<optional: 'ok', or the tooling problem, e.g. 'frontmatter-validator-timeout'>",
|
|
46
|
+
"state2Counts": {"provenance": N, "metadata": N, "union": N} # optional, State 2
|
|
32
47
|
}
|
|
33
48
|
|
|
34
|
-
Verdict
|
|
49
|
+
Verdict order (each step reads the one before it):
|
|
50
|
+
1. Score: redistribute the weights, weigh the scores, total them. On a
|
|
51
|
+
State 2 run of a skill that is not a stack, with state2Counts, the
|
|
52
|
+
State 2 undercount deduction comes first: when the provenance and
|
|
53
|
+
metadata union exceeds either source by more than STATE2_DIVERGENCE_PCT
|
|
54
|
+
percent, Export Coverage loses STATE2_DEDUCTION points (never below 0),
|
|
55
|
+
and `state2Deduction` and `scoringNotes` say so.
|
|
56
|
+
2. Minimum-evidence floor: too few active categories make the result
|
|
57
|
+
INCONCLUSIVE, which nothing below overrides.
|
|
58
|
+
3. PASS or FAIL: totalScore against the threshold (`result`).
|
|
59
|
+
4. Caps: Cap 1 (any toolingStatus other than "ok") and Cap 2 (docs-only
|
|
60
|
+
with no external validation score) turn a PASS into FAIL.
|
|
61
|
+
5. Threshold fallback: an uncapped FAIL whose totalScore is 80 or more,
|
|
62
|
+
against a threshold above 80, passes at the 80 floor. A run a cap
|
|
63
|
+
turned to FAIL stays FAIL, whatever the threshold.
|
|
64
|
+
|
|
65
|
+
Verdict override (post-score caps and threshold fallback, steps 4 and 5):
|
|
35
66
|
When a cap or the threshold fallback engages, the output additionally carries
|
|
36
67
|
`effectiveResult` (the final verdict after caps/fallback), `capReason`
|
|
37
68
|
(string or null), `thresholdFallback` (bool), and `originalThreshold` (the
|
|
@@ -40,16 +71,16 @@ Verdict override (post-score caps + threshold fallback — see compute_score):
|
|
|
40
71
|
always the pre-cap/pre-fallback score-vs-threshold (or evidence-floor) verdict.
|
|
41
72
|
|
|
42
73
|
Exit codes (same convention as reconcile-coverage.py):
|
|
43
|
-
0
|
|
44
|
-
1
|
|
45
|
-
2
|
|
74
|
+
0 a score was computed; the verdict may be PASS/FAIL/INCONCLUSIVE
|
|
75
|
+
1 input could not be parsed at all (no input provided, or malformed JSON)
|
|
76
|
+
2 input parsed but schema/semantics invalid, or a score file refused
|
|
46
77
|
|
|
47
78
|
Both 1 and 2 emit an {"error": ..., "code": "INVALID_INPUT"} envelope on stdout,
|
|
48
79
|
so the presence of that envelope — not the specific code — is what tells a caller
|
|
49
80
|
the input was refused. A refused input is distinct from an unavailable script:
|
|
50
81
|
the numbers handed in are wrong and must be fixed, NOT hand-computed into a total
|
|
51
|
-
from the values this script declined to score. score.md §3c keys
|
|
52
|
-
|
|
82
|
+
from the values this script declined to score. score.md §3c keys on that
|
|
83
|
+
distinction: a refused input is corrected, a script that does not run halts.
|
|
53
84
|
"""
|
|
54
85
|
|
|
55
86
|
from __future__ import annotations
|
|
@@ -58,8 +89,9 @@ import argparse
|
|
|
58
89
|
import json
|
|
59
90
|
import math
|
|
60
91
|
import sys
|
|
92
|
+
from pathlib import Path
|
|
61
93
|
|
|
62
|
-
# --- Weight Tables
|
|
94
|
+
# --- Weight Tables ---
|
|
63
95
|
|
|
64
96
|
CONTEXTUAL_WEIGHTS = {
|
|
65
97
|
"exportCoverage": 36,
|
|
@@ -86,6 +118,21 @@ CATEGORIES = [
|
|
|
86
118
|
]
|
|
87
119
|
|
|
88
120
|
DEFAULT_THRESHOLD = 80
|
|
121
|
+
# The threshold fallback's floor: an uncapped FAIL scoring this much passes
|
|
122
|
+
# when the threshold is above it.
|
|
123
|
+
FALLBACK_FLOOR = 80
|
|
124
|
+
|
|
125
|
+
# The source access levels a run reports (source-access-protocol.md). Tooling
|
|
126
|
+
# health is not one of them: it is toolingStatus.
|
|
127
|
+
ANALYSIS_CONFIDENCE = ("full", "provenance-map", "metadata-only", "remote-only", "docs-only")
|
|
128
|
+
# States 3 and 4: no local source, so no signature or type to compare.
|
|
129
|
+
NO_LOCAL_SOURCE = ("metadata-only", "remote-only")
|
|
130
|
+
TOOLING_OK = "ok"
|
|
131
|
+
|
|
132
|
+
# State 2 undercount risk: the provenance map is a frozen snapshot, so when it
|
|
133
|
+
# and metadata.json disagree by more than this, Export Coverage is reduced.
|
|
134
|
+
STATE2_DIVERGENCE_PCT = 5
|
|
135
|
+
STATE2_DEDUCTION = 10
|
|
89
136
|
|
|
90
137
|
|
|
91
138
|
# --- Redistribution equivalence classes (moved here from scoring-rules.md) ---
|
|
@@ -94,8 +141,9 @@ DEFAULT_THRESHOLD = 80
|
|
|
94
141
|
# `weights` / `activeCategories` / `skippedCategories` output depends ONLY on
|
|
95
142
|
# three things: the base table (contextual vs naive), whether Signature Accuracy
|
|
96
143
|
# + Type Coverage are skipped (Quick tier OR docsOnly OR state2 OR stackSkill OR
|
|
97
|
-
# referenceApp), and whether External Validation is skipped
|
|
98
|
-
# is null). Every (mode × tier × docsOnly × state2 ×
|
|
144
|
+
# referenceApp OR State 3/4), and whether External Validation is skipped
|
|
145
|
+
# (externalValidation is null). Every (mode × tier × docsOnly × state2 ×
|
|
146
|
+
# stackSkill × referenceApp × analysisConfidence)
|
|
99
147
|
# cell with an identical skip set + base table therefore reduces to the same
|
|
100
148
|
# equivalence class below and emits identical final weights for identical input
|
|
101
149
|
# scores — only the `skipReasons` string differs. The prompt (score.md) never
|
|
@@ -133,12 +181,14 @@ DEFAULT_THRESHOLD = 80
|
|
|
133
181
|
# | 25 | naive | Quick | F | T | naive | Quick + State 2 | if null | D | (equiv.) |
|
|
134
182
|
# | 26 | naive | * | T | T | naive | docs-only + State 2 | if null | D | (equiv.) |
|
|
135
183
|
#
|
|
136
|
-
# Stack skills (stackSkill)
|
|
137
|
-
#
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
#
|
|
141
|
-
#
|
|
184
|
+
# Stack skills (stackSkill), reference-app skills (referenceApp) and State 3/4
|
|
185
|
+
# runs (analysisConfidence metadata-only or remote-only) share the skip set of
|
|
186
|
+
# the docsOnly/state2 rows: contextual → class B, naive → class D; only
|
|
187
|
+
# skipReasons differs (fixtures suite_u_stack_skill_deep,
|
|
188
|
+
# suite_v_reference_app_deep, suite_ab_naive_forge_metadata_only). Quick-tier
|
|
189
|
+
# rows (4, 8, 12, 17, 21, 25) then feed the minimum-evidence floor in the
|
|
190
|
+
# result block below (§7 of compute_score), which can force INCONCLUSIVE after
|
|
191
|
+
# this pre-floor redistribution.
|
|
142
192
|
|
|
143
193
|
|
|
144
194
|
# --- Helpers ---
|
|
@@ -205,6 +255,22 @@ def validate_input(inp):
|
|
|
205
255
|
if val is not None and not isinstance(val, str):
|
|
206
256
|
return f"{str_field} must be a string or null, got: {type(val).__name__}"
|
|
207
257
|
|
|
258
|
+
confidence = inp.get("analysisConfidence")
|
|
259
|
+
if confidence == "degraded":
|
|
260
|
+
return ("analysisConfidence no longer takes 'degraded': it names the source access only. "
|
|
261
|
+
"Pass the tooling problem as toolingStatus (for example 'frontmatter-validator-timeout').")
|
|
262
|
+
if confidence is not None and confidence not in ANALYSIS_CONFIDENCE:
|
|
263
|
+
return f"analysisConfidence must be one of: {', '.join(ANALYSIS_CONFIDENCE)}"
|
|
264
|
+
|
|
265
|
+
counts = inp.get("state2Counts")
|
|
266
|
+
if counts is not None:
|
|
267
|
+
if not isinstance(counts, dict):
|
|
268
|
+
return "state2Counts must be an object {provenance, metadata, union}"
|
|
269
|
+
for key in ("provenance", "metadata", "union"):
|
|
270
|
+
val = counts.get(key)
|
|
271
|
+
if isinstance(val, bool) or not isinstance(val, int) or val < 0:
|
|
272
|
+
return f"state2Counts.{key} must be an integer >= 0"
|
|
273
|
+
|
|
208
274
|
for cat in CATEGORIES:
|
|
209
275
|
score = inp["scores"].get(cat)
|
|
210
276
|
if score is not None:
|
|
@@ -231,6 +297,7 @@ def compute_score(inp):
|
|
|
231
297
|
state2 = inp.get("state2") is True
|
|
232
298
|
stack_skill = inp.get("stackSkill") is True
|
|
233
299
|
reference_app = inp.get("referenceApp") is True
|
|
300
|
+
no_local_source = inp.get("analysisConfidence") in NO_LOCAL_SOURCE
|
|
234
301
|
threshold = inp.get("threshold") if inp.get("threshold") is not None else DEFAULT_THRESHOLD
|
|
235
302
|
scores = inp["scores"]
|
|
236
303
|
|
|
@@ -239,7 +306,7 @@ def compute_score(inp):
|
|
|
239
306
|
|
|
240
307
|
# 3. Determine skip set
|
|
241
308
|
skip_reasons = {}
|
|
242
|
-
skip_sig_type = tier == "Quick" or docs_only or state2 or stack_skill or reference_app
|
|
309
|
+
skip_sig_type = tier == "Quick" or docs_only or state2 or stack_skill or reference_app or no_local_source
|
|
243
310
|
|
|
244
311
|
if skip_sig_type:
|
|
245
312
|
reasons = []
|
|
@@ -253,6 +320,8 @@ def compute_score(inp):
|
|
|
253
320
|
reasons.append("stack skill (external type surface)")
|
|
254
321
|
if reference_app:
|
|
255
322
|
reasons.append("reference-app (no library export signatures)")
|
|
323
|
+
if no_local_source:
|
|
324
|
+
reasons.append("no local source (State 3/4)")
|
|
256
325
|
reason = " + ".join(reasons)
|
|
257
326
|
skip_reasons["signatureAccuracy"] = reason
|
|
258
327
|
skip_reasons["typeCoverage"] = reason
|
|
@@ -293,13 +362,41 @@ def compute_score(inp):
|
|
|
293
362
|
else:
|
|
294
363
|
final_weights[cat] = round2((adjusted_weights[cat] / sum_active_weights) * 100)
|
|
295
364
|
|
|
365
|
+
# 4b. State 2 undercount deduction: a frozen provenance map that disagrees
|
|
366
|
+
# with its own metadata may hide source exports, so prefer understating.
|
|
367
|
+
effective_scores = dict(scores)
|
|
368
|
+
scoring_notes = []
|
|
369
|
+
state2_deduction = None
|
|
370
|
+
counts = inp.get("state2Counts")
|
|
371
|
+
if state2 and not stack_skill and counts is not None:
|
|
372
|
+
sources = [counts["provenance"], counts["metadata"]]
|
|
373
|
+
divergence = 0.0
|
|
374
|
+
if all(sources):
|
|
375
|
+
divergence = round2(max((counts["union"] - n) / n * 100 for n in sources))
|
|
376
|
+
applied = divergence > STATE2_DIVERGENCE_PCT
|
|
377
|
+
raw = scores["exportCoverage"]
|
|
378
|
+
adjusted = max(0, raw - STATE2_DEDUCTION) if applied else raw
|
|
379
|
+
effective_scores["exportCoverage"] = adjusted
|
|
380
|
+
state2_deduction = {
|
|
381
|
+
"divergencePct": divergence,
|
|
382
|
+
"applied": applied,
|
|
383
|
+
"rawExportCoverage": raw,
|
|
384
|
+
"exportCoverage": adjusted,
|
|
385
|
+
}
|
|
386
|
+
if applied:
|
|
387
|
+
scoring_notes.append(
|
|
388
|
+
f"State 2 undercount risk acknowledged: {STATE2_DEDUCTION}-point deduction applied to "
|
|
389
|
+
f"Export Coverage (raw: {raw}%, adjusted: {adjusted}%; provenance and metadata "
|
|
390
|
+
f"diverge by {divergence}%)"
|
|
391
|
+
)
|
|
392
|
+
|
|
296
393
|
# 5. Compute weighted scores
|
|
297
394
|
weighted_scores = {}
|
|
298
395
|
for cat in CATEGORIES:
|
|
299
396
|
if final_weights[cat] == 0:
|
|
300
397
|
weighted_scores[cat] = 0
|
|
301
398
|
else:
|
|
302
|
-
weighted_scores[cat] = round2((final_weights[cat] / 100) *
|
|
399
|
+
weighted_scores[cat] = round2((final_weights[cat] / 100) * effective_scores[cat])
|
|
303
400
|
|
|
304
401
|
# 6. Compute total
|
|
305
402
|
total_score = round2(sum(weighted_scores[cat] for cat in CATEGORIES))
|
|
@@ -310,7 +407,7 @@ def compute_score(inp):
|
|
|
310
407
|
# 7. Determine result — MINIMUM-EVIDENCE FLOOR first, then PASS/FAIL.
|
|
311
408
|
# skf-test-skill grades other skills; a false PASS is catastrophic.
|
|
312
409
|
# If the evidence base is too thin to cross-validate itself, force
|
|
313
|
-
# INCONCLUSIVE (a gate, not a pass/fail).
|
|
410
|
+
# INCONCLUSIVE (a gate, not a pass/fail).
|
|
314
411
|
active_categories = [cat for cat in CATEGORIES if final_weights[cat] > 0]
|
|
315
412
|
skipped_categories = [
|
|
316
413
|
cat for cat in CATEGORIES if cat in skipped_set or base_weights[cat] == 0
|
|
@@ -352,12 +449,10 @@ def compute_score(inp):
|
|
|
352
449
|
else:
|
|
353
450
|
result = "PASS" if total_score >= threshold else "FAIL"
|
|
354
451
|
|
|
355
|
-
# 7b. Post-score caps
|
|
356
|
-
#
|
|
357
|
-
# and unit-tested here rather than re-derived in the prompt. The minimum-
|
|
452
|
+
# 7b. Post-score caps, then the threshold fallback: the deterministic verdict
|
|
453
|
+
# override, kept here so the prompt never re-derives it. The minimum-
|
|
358
454
|
# evidence floor (INCONCLUSIVE) is a gate that NO cap or fallback may override,
|
|
359
455
|
# so all of this is skipped when result == "INCONCLUSIVE".
|
|
360
|
-
analysis_confidence = inp.get("analysisConfidence")
|
|
361
456
|
tooling_status = inp.get("toolingStatus")
|
|
362
457
|
|
|
363
458
|
effective_result = result
|
|
@@ -368,15 +463,13 @@ def compute_score(inp):
|
|
|
368
463
|
|
|
369
464
|
if result != "INCONCLUSIVE":
|
|
370
465
|
cap_reasons = []
|
|
371
|
-
# Cap 1
|
|
372
|
-
#
|
|
373
|
-
|
|
374
|
-
isinstance(tooling_status, str) and "missing" in tooling_status
|
|
375
|
-
)
|
|
376
|
-
if tooling_degraded:
|
|
466
|
+
# Cap 1: tooling degraded, any toolingStatus other than "ok" (a missing
|
|
467
|
+
# helper, or a validator that timed out). Absent means not reported.
|
|
468
|
+
if tooling_status is not None and tooling_status != TOOLING_OK:
|
|
377
469
|
cap_fired = True
|
|
378
470
|
cap_reasons.append(
|
|
379
|
-
"tooling degraded
|
|
471
|
+
f"tooling degraded (toolingStatus: {tooling_status}): capped below threshold "
|
|
472
|
+
"until the tooling is restored"
|
|
380
473
|
)
|
|
381
474
|
# Cap 2 — docs-only mode with no external validators available.
|
|
382
475
|
if docs_only and scores.get("externalValidation") is None:
|
|
@@ -390,14 +483,12 @@ def compute_score(inp):
|
|
|
390
483
|
if result == "PASS":
|
|
391
484
|
effective_result = "FAIL"
|
|
392
485
|
|
|
393
|
-
# Threshold fallback
|
|
394
|
-
#
|
|
395
|
-
#
|
|
396
|
-
#
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
# decision — raw is used for spec fidelity and testability.
|
|
400
|
-
if effective_result == "FAIL" and total_score >= 80 and threshold > 80:
|
|
486
|
+
# Threshold fallback: an uncapped FAIL passes at the 80 floor when
|
|
487
|
+
# totalScore clears 80 and the threshold was above 80. A cap means the
|
|
488
|
+
# evidence cannot be trusted at any threshold, so a capped run stays
|
|
489
|
+
# FAIL: a threshold above 80 is a target, never a way past a cap.
|
|
490
|
+
if (effective_result == "FAIL" and not cap_fired and total_score >= FALLBACK_FLOOR
|
|
491
|
+
and threshold > FALLBACK_FLOOR):
|
|
401
492
|
threshold_fallback = True
|
|
402
493
|
original_threshold = threshold
|
|
403
494
|
effective_result = "PASS"
|
|
@@ -439,6 +530,12 @@ def compute_score(inp):
|
|
|
439
530
|
output["thresholdFallback"] = threshold_fallback
|
|
440
531
|
output["originalThreshold"] = original_threshold
|
|
441
532
|
|
|
533
|
+
if state2_deduction is not None:
|
|
534
|
+
output["state2Deduction"] = state2_deduction
|
|
535
|
+
|
|
536
|
+
if scoring_notes:
|
|
537
|
+
output["scoringNotes"] = scoring_notes
|
|
538
|
+
|
|
442
539
|
if warnings:
|
|
443
540
|
output["warnings"] = warnings
|
|
444
541
|
|
|
@@ -484,9 +581,67 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
484
581
|
action="store_true",
|
|
485
582
|
help="Read the JSON object from stdin.",
|
|
486
583
|
)
|
|
584
|
+
files = parser.add_argument_group("score files (each wins over the same JSON field)")
|
|
585
|
+
files.add_argument("--coverage", metavar="PATH", help="reconcile-coverage.py output: exportCoverage")
|
|
586
|
+
files.add_argument("--signatures", metavar="PATH",
|
|
587
|
+
help="score-signatures.py score output: signatureAccuracy and typeCoverage")
|
|
588
|
+
files.add_argument("--coherence", metavar="PATH", help="aggregate-coherence.py output: combinedCoherence")
|
|
589
|
+
files.add_argument("--external", metavar="PATH", help="combine-external-scores.py output: externalScore")
|
|
590
|
+
files.add_argument("--surface", metavar="PATH", help="load-coverage-inputs.py surface output: state2")
|
|
487
591
|
return parser
|
|
488
592
|
|
|
489
593
|
|
|
594
|
+
# (flag, [(field in the file, category it fills)]); a null field leaves the category null.
|
|
595
|
+
SCORE_FILES = (
|
|
596
|
+
("coverage", [("exportCoverage", "exportCoverage")]),
|
|
597
|
+
("signatures", [("signatureAccuracy", "signatureAccuracy"), ("typeCoverage", "typeCoverage")]),
|
|
598
|
+
("coherence", [("combinedCoherence", "coherence")]),
|
|
599
|
+
("external", [("externalScore", "externalValidation")]),
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
class ScoreFileError(Exception):
|
|
604
|
+
"""A score file that cannot be read or does not hold its score."""
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _read_score_file(path, flag):
|
|
608
|
+
try:
|
|
609
|
+
data = json.loads(Path(path).read_bytes().decode("utf-8-sig"))
|
|
610
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
611
|
+
raise ScoreFileError(f"cannot read --{flag} {path}: {exc}") from exc
|
|
612
|
+
except json.JSONDecodeError as exc:
|
|
613
|
+
raise ScoreFileError(f"--{flag} {path} is not JSON: {exc.msg}") from exc
|
|
614
|
+
if not isinstance(data, dict):
|
|
615
|
+
raise ScoreFileError(f"--{flag} {path} is not a JSON object")
|
|
616
|
+
if "error" in data or data.get("valid") is False:
|
|
617
|
+
raise ScoreFileError(f"--{flag} {path} holds a refused result, not a score")
|
|
618
|
+
return data
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def apply_score_files(inp, args):
|
|
622
|
+
"""Fill `scores` and `state2Counts` from the score files the flags name."""
|
|
623
|
+
for flag, fields in SCORE_FILES:
|
|
624
|
+
path = getattr(args, flag)
|
|
625
|
+
if path is None:
|
|
626
|
+
continue
|
|
627
|
+
data = _read_score_file(path, flag)
|
|
628
|
+
scores = inp.setdefault("scores", {})
|
|
629
|
+
if not isinstance(scores, dict):
|
|
630
|
+
raise ScoreFileError("scores must be an object")
|
|
631
|
+
for field, category in fields:
|
|
632
|
+
if field not in data:
|
|
633
|
+
raise ScoreFileError(f"--{flag} {path} has no `{field}`")
|
|
634
|
+
scores[category] = data[field]
|
|
635
|
+
if args.surface is not None:
|
|
636
|
+
data = _read_score_file(args.surface, "surface")
|
|
637
|
+
state2 = data.get("state2")
|
|
638
|
+
if isinstance(state2, dict):
|
|
639
|
+
inp["state2Counts"] = {"provenance": state2.get("provenanceCount"),
|
|
640
|
+
"metadata": state2.get("metadataCount"),
|
|
641
|
+
"union": state2.get("unionCount")}
|
|
642
|
+
return inp
|
|
643
|
+
|
|
644
|
+
|
|
490
645
|
def _resolve_input(args: argparse.Namespace) -> str:
|
|
491
646
|
if args.stdin:
|
|
492
647
|
return sys.stdin.read()
|
|
@@ -512,17 +667,40 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
512
667
|
print(json.dumps(make_error(f"Invalid JSON: {exc.msg}"), indent=2))
|
|
513
668
|
return 1
|
|
514
669
|
|
|
670
|
+
if isinstance(data, dict):
|
|
671
|
+
try:
|
|
672
|
+
data = apply_score_files(data, args)
|
|
673
|
+
except ScoreFileError as exc:
|
|
674
|
+
print(json.dumps(make_error(str(exc)), indent=2))
|
|
675
|
+
return 2
|
|
676
|
+
|
|
515
677
|
result = compute_score(data)
|
|
516
678
|
print(json.dumps(result, indent=2))
|
|
517
679
|
# A rejected input exits 2, matching reconcile-coverage.py. Exiting 0 here
|
|
518
680
|
# made a malformed scoring input indistinguishable from a scored run, so the
|
|
519
681
|
# error envelope could be skimmed past and the score hand-computed from the
|
|
520
|
-
# very numbers the script refused. score.md §3c keys
|
|
521
|
-
#
|
|
682
|
+
# very numbers the script refused. score.md §3c keys on this distinction: a
|
|
683
|
+
# refused input is corrected, a script that does not run halts.
|
|
522
684
|
if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
|
|
523
685
|
return 2
|
|
524
686
|
return 0
|
|
525
687
|
|
|
526
688
|
|
|
689
|
+
def _force_utf8(*streams) -> None:
|
|
690
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
691
|
+
|
|
692
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
693
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
694
|
+
"""
|
|
695
|
+
for stream in streams:
|
|
696
|
+
if hasattr(stream, "reconfigure"):
|
|
697
|
+
errors = getattr(stream, "errors", None)
|
|
698
|
+
if errors is None:
|
|
699
|
+
stream.reconfigure(encoding="utf-8")
|
|
700
|
+
else:
|
|
701
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
702
|
+
|
|
703
|
+
|
|
527
704
|
if __name__ == "__main__":
|
|
705
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
528
706
|
raise SystemExit(main())
|