bmad-module-skill-forge 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +15 -5
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -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 Documented-vs-Source coverage reconciliation.
|
|
6
6
|
|
|
@@ -12,28 +12,35 @@ already-extracted name lists (the §1 documented inventory and the §2 source
|
|
|
12
12
|
barrel) into a reproducible {documented, missing, stale, exportCoverage}
|
|
13
13
|
result, so the numerator no longer swings between runs on split-body skills.
|
|
14
14
|
|
|
15
|
-
It covers the
|
|
15
|
+
It covers the four branches §2c enumerates:
|
|
16
16
|
|
|
17
17
|
* "barrel" (enumerated path) — intersect documented_set against the source
|
|
18
18
|
barrel_set: Documented = |documented_set ∩ barrel_set|,
|
|
19
19
|
Missing = barrel_set − documented_set, Stale = documented_set − barrel_set,
|
|
20
20
|
Export Coverage = |Documented| / |barrel_set| * 100.
|
|
21
21
|
|
|
22
|
-
* "scalar" (§
|
|
23
|
-
|
|
22
|
+
* "scalar" (§2b priority-1 effective_denominator, no enumerated name set):
|
|
23
|
+
look each documented name up in SKILL.md ∪ references/*.md;
|
|
24
24
|
Documented = count present, denominator = effective_denominator,
|
|
25
25
|
Missing = max(0, denominator − Documented), Stale not enumerable (empty),
|
|
26
26
|
Export Coverage = min(100, Documented / denominator * 100).
|
|
27
27
|
|
|
28
|
-
* "stack" (skill_type == "stack", empty source barrel)
|
|
28
|
+
* "stack" (skill_type == "stack", empty source barrel): look each
|
|
29
29
|
composition-surface name (provenance-map cited contracts, ::-excluded, or
|
|
30
|
-
libraries + integration_pairs
|
|
30
|
+
libraries + integration_pairs, from load-coverage-inputs.py) up in
|
|
31
31
|
SKILL.md ∪ references/*.md; denominator = stack_denominator,
|
|
32
32
|
Missing = max(0, denominator − Documented), Stale not enumerable,
|
|
33
33
|
Export Coverage = min(100, Documented / stack_denominator * 100).
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
* "docsOnly" (a skill whose citations are all [EXT:...], or no source
|
|
36
|
+
access): no source to compare against, so Export Coverage measures
|
|
37
|
+
documentation completeness: complete items / documented items * 100,
|
|
38
|
+
one item per name and kind of the §1 inventory, complete when it has a
|
|
39
|
+
description and, for a function or method, params and a return type
|
|
40
|
+
(validate-inventory.py's rule).
|
|
41
|
+
|
|
42
|
+
The two lookup branches bound their outputs because the numerator and the
|
|
43
|
+
denominator are independent measures of different sets: the lookup count can
|
|
37
44
|
exceed a consumer-surface denominator without either being wrong. Unbounded,
|
|
38
45
|
that produced a negative Missing and a >100% coverage that compute-score.py
|
|
39
46
|
rejects as out of range. `documented` is left as the true count (it is reported
|
|
@@ -41,54 +48,101 @@ verbatim as "Documented in SKILL.md"); the derived ratio is what gets bounded,
|
|
|
41
48
|
and `numeratorSurplus` / `coverageUncapped` / `coverageCapped` report the
|
|
42
49
|
overshoot so a deflated denominator stays visible instead of being swallowed.
|
|
43
50
|
|
|
51
|
+
A name counts as written when SKILL.md or a .md file directly in references/
|
|
52
|
+
holds it as a fixed string, case-sensitive, never inside a longer identifier
|
|
53
|
+
(`get` is not found in `target` or `getAll`): the rule validate-inventory.py
|
|
54
|
+
defines, which this script loads from beside it.
|
|
55
|
+
|
|
44
56
|
CLI usage:
|
|
57
|
+
uv run reconcile-coverage.py --denominator-source <source> [file flags] --output <file>
|
|
45
58
|
uv run reconcile-coverage.py '<JSON>' # JSON literal positional
|
|
46
59
|
uv run reconcile-coverage.py --json-input '<JSON>' # explicit flag form
|
|
47
60
|
cat input.json | uv run reconcile-coverage.py --stdin # piped input
|
|
48
61
|
|
|
62
|
+
File flags read the inputs from the run folder instead of a JSON payload;
|
|
63
|
+
each sets the input field it names and wins over the same field in the JSON:
|
|
64
|
+
--denominator-source denominatorSource
|
|
65
|
+
--denominator-value denominatorValue (scalar), when no --coverage-inputs
|
|
66
|
+
file gives it
|
|
67
|
+
--skill-dir skillPackagePath (scalar, stack)
|
|
68
|
+
--inventory PATH exports: the validated inventory validate-inventory.py
|
|
69
|
+
wrote with --output
|
|
70
|
+
--surface PATH barrelSet: one name set of load-coverage-inputs.py
|
|
71
|
+
surface, chosen by --surface-set (default "all")
|
|
72
|
+
--coverage-inputs PATH load-coverage-inputs.py metadata output: for the
|
|
73
|
+
scalar branch, denominatorValue from its
|
|
74
|
+
`effectiveDenominator` (stats.effective_denominator);
|
|
75
|
+
for the stack branch, compositionNames and
|
|
76
|
+
denominatorValue from its `stack` object
|
|
77
|
+
--verified PATH scalar: verify-declared-numerator.py's result; when it
|
|
78
|
+
found the declared count inflated, its `verified`
|
|
79
|
+
count is the numerator
|
|
80
|
+
--output PATH also write the result to this file (UTF-8 JSON); a
|
|
81
|
+
refused input removes a file left there earlier
|
|
82
|
+
|
|
49
83
|
Input schema (one object):
|
|
50
84
|
{
|
|
51
|
-
"denominatorSource": "barrel" | "scalar" | "stack",
|
|
85
|
+
"denominatorSource": "barrel" | "scalar" | "stack" | "docsOnly", # required
|
|
52
86
|
"exports": [ {"name": "...", "kind": "..."}, ... ], # §1 inventory
|
|
53
87
|
"barrelSet": ["a", "b", ...], # barrel: resolved name set
|
|
54
88
|
"perFileResults": [ {"exports_found": ["a", ...]} ], # barrel: union'd if no barrelSet
|
|
55
89
|
"denominatorValue": <int>, # scalar/stack: resolved denominator
|
|
56
|
-
"compositionNames": ["lib::x", ...], # stack: names to
|
|
57
|
-
"skillPackagePath": "/path/to/skill"
|
|
90
|
+
"compositionNames": ["lib::x", ...], # stack: names to look up
|
|
91
|
+
"skillPackagePath": "/path/to/skill", # scalar/stack: SKILL.md ∪ references
|
|
92
|
+
"verifiedNumerator": <int> # scalar: overrides the lookup count
|
|
58
93
|
}
|
|
59
94
|
|
|
60
95
|
Output (stdout, one object):
|
|
61
96
|
{
|
|
62
|
-
"branch": "enumerated" | "scalar" | "stack",
|
|
63
|
-
"denominatorSource": "barrel" | "scalar" | "stack",
|
|
97
|
+
"branch": "enumerated" | "scalar" | "stack" | "docsOnly",
|
|
98
|
+
"denominatorSource": "barrel" | "scalar" | "stack" | "docsOnly",
|
|
64
99
|
"denominator": <int>,
|
|
65
100
|
"documented": <int>,
|
|
66
|
-
"missing": [names], # enumerated: source names not documented;
|
|
101
|
+
"missing": [names], # enumerated: source names not documented; others: []
|
|
67
102
|
"missingCount": <int>, # always present; never negative
|
|
68
|
-
"stale": [names], # enumerated: documented names not in source;
|
|
103
|
+
"stale": [names], # enumerated: documented names not in source; others: []
|
|
69
104
|
"staleCount": <int>, # always present
|
|
70
|
-
"staleApplicable": <bool>, # false for scalar/stack (no barrel to enumerate)
|
|
105
|
+
"staleApplicable": <bool>, # false for scalar/stack/docsOnly (no barrel to enumerate)
|
|
71
106
|
"exportCoverage": <float>, # scalar/stack: capped at 100
|
|
72
107
|
"numeratorSurplus": <int>, # scalar/stack only: max(0, documented − denominator)
|
|
73
108
|
"coverageUncapped": <float>, # scalar/stack only: the ratio before the cap
|
|
74
|
-
"coverageCapped": <bool
|
|
109
|
+
"coverageCapped": <bool>, # scalar/stack only: true when the cap bound the ratio
|
|
110
|
+
"numeratorSource": "lookup" | "verified", # scalar only
|
|
111
|
+
"incomplete": [{"name", "kind", "missing"}] # docsOnly only: the incomplete items
|
|
75
112
|
}
|
|
76
113
|
|
|
77
114
|
Exit codes:
|
|
78
|
-
0
|
|
79
|
-
1
|
|
80
|
-
2
|
|
115
|
+
0 reconciliation emitted successfully
|
|
116
|
+
1 no input, input that does not parse as JSON, or an input file that cannot be read
|
|
117
|
+
2 input parsed but schema/semantics invalid (error object emitted as JSON)
|
|
81
118
|
"""
|
|
82
119
|
|
|
83
120
|
from __future__ import annotations
|
|
84
121
|
|
|
85
122
|
import argparse
|
|
123
|
+
import importlib.util
|
|
86
124
|
import json
|
|
87
125
|
import math
|
|
88
126
|
import sys
|
|
89
127
|
from pathlib import Path
|
|
90
128
|
|
|
91
|
-
VALID_SOURCES = ("barrel", "scalar", "stack")
|
|
129
|
+
VALID_SOURCES = ("barrel", "scalar", "stack", "docsOnly")
|
|
130
|
+
|
|
131
|
+
_INVENTORY_MODULE = None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _inventory():
|
|
135
|
+
"""validate-inventory.py beside this script: the name match and completeness rules live there."""
|
|
136
|
+
global _INVENTORY_MODULE
|
|
137
|
+
if _INVENTORY_MODULE is None:
|
|
138
|
+
sibling = Path(__file__).resolve().parent / "validate-inventory.py"
|
|
139
|
+
spec = importlib.util.spec_from_file_location("skf_validate_inventory", sibling)
|
|
140
|
+
if spec is None or spec.loader is None or not sibling.is_file():
|
|
141
|
+
raise ImportError(f"validate-inventory.py not found beside {Path(__file__).name}")
|
|
142
|
+
module = importlib.util.module_from_spec(spec)
|
|
143
|
+
spec.loader.exec_module(module)
|
|
144
|
+
_INVENTORY_MODULE = module
|
|
145
|
+
return _INVENTORY_MODULE
|
|
92
146
|
|
|
93
147
|
|
|
94
148
|
def round2(value):
|
|
@@ -135,34 +189,17 @@ def build_barrel_set(inp):
|
|
|
135
189
|
return barrel
|
|
136
190
|
|
|
137
191
|
|
|
138
|
-
# ---
|
|
192
|
+
# --- Documented-name lookup ---------------------------------------------------
|
|
139
193
|
|
|
140
194
|
|
|
141
195
|
def load_doc_text(skill_package_path):
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
Deterministic: references are read in sorted order. Reads as UTF-8 so
|
|
145
|
-
non-ASCII exports do not mojibake on Windows (cp1252 default).
|
|
146
|
-
"""
|
|
147
|
-
root = Path(skill_package_path)
|
|
148
|
-
parts = []
|
|
149
|
-
skill_md = root / "SKILL.md"
|
|
150
|
-
if skill_md.is_file():
|
|
151
|
-
parts.append(skill_md.read_text(encoding="utf-8"))
|
|
152
|
-
refs_dir = root / "references"
|
|
153
|
-
if refs_dir.is_dir():
|
|
154
|
-
for ref in sorted(refs_dir.glob("*.md")):
|
|
155
|
-
parts.append(ref.read_text(encoding="utf-8"))
|
|
156
|
-
return "\n".join(parts)
|
|
196
|
+
"""SKILL.md ∪ references/*.md text, as validate-inventory.py reads it."""
|
|
197
|
+
return _inventory().load_doc_text(skill_package_path)
|
|
157
198
|
|
|
158
199
|
|
|
159
200
|
def names_present(names, doc_text):
|
|
160
|
-
"""
|
|
161
|
-
|
|
162
|
-
Substring containment, case-sensitive — mirrors `grep "{name}"`.
|
|
163
|
-
"""
|
|
164
|
-
present = {n for n in names if isinstance(n, str) and n and n in doc_text}
|
|
165
|
-
return sorted(present)
|
|
201
|
+
"""The sorted, de-duplicated `names` the doc text writes (validate-inventory.py's match)."""
|
|
202
|
+
return _inventory().names_present(names, doc_text)
|
|
166
203
|
|
|
167
204
|
|
|
168
205
|
# --- Core reconciliation ----------------------------------------------------
|
|
@@ -180,17 +217,46 @@ def _validate(inp):
|
|
|
180
217
|
if source == "barrel":
|
|
181
218
|
if inp.get("barrelSet") is None and inp.get("perFileResults") is None:
|
|
182
219
|
return "barrel branch requires either `barrelSet` or `perFileResults`"
|
|
220
|
+
elif source == "docsOnly":
|
|
221
|
+
if not isinstance(inp.get("exports"), list):
|
|
222
|
+
return "docsOnly branch requires the §1 inventory `exports` (or --inventory)"
|
|
183
223
|
else: # scalar / stack
|
|
184
224
|
dv = inp.get("denominatorValue")
|
|
185
225
|
if not isinstance(dv, int) or isinstance(dv, bool) or dv < 0:
|
|
186
226
|
return f"{source} branch requires integer `denominatorValue` >= 0"
|
|
187
227
|
if not inp.get("skillPackagePath"):
|
|
188
|
-
return f"{source} branch requires `skillPackagePath` to
|
|
228
|
+
return f"{source} branch requires `skillPackagePath` to read SKILL.md ∪ references"
|
|
189
229
|
if source == "stack" and inp.get("compositionNames") is None:
|
|
190
|
-
return "stack branch requires `compositionNames` (composition-surface names to
|
|
230
|
+
return "stack branch requires `compositionNames` (composition-surface names to look up)"
|
|
231
|
+
verified = inp.get("verifiedNumerator")
|
|
232
|
+
if verified is not None and (not isinstance(verified, int) or isinstance(verified, bool) or verified < 0):
|
|
233
|
+
return "verifiedNumerator must be an integer >= 0"
|
|
191
234
|
return None
|
|
192
235
|
|
|
193
236
|
|
|
237
|
+
def _docs_only(inp):
|
|
238
|
+
tally = _inventory().completeness(inp["exports"])
|
|
239
|
+
total = tally["total"]
|
|
240
|
+
if total == 0:
|
|
241
|
+
return make_error(
|
|
242
|
+
"docsOnly branch: the inventory documents no item, so Export Coverage is "
|
|
243
|
+
"undefined; the §2b docs-only guard should HALT"
|
|
244
|
+
)
|
|
245
|
+
return {
|
|
246
|
+
"branch": "docsOnly",
|
|
247
|
+
"denominatorSource": "docsOnly",
|
|
248
|
+
"denominator": total,
|
|
249
|
+
"documented": tally["complete"],
|
|
250
|
+
"missing": [],
|
|
251
|
+
"missingCount": total - tally["complete"],
|
|
252
|
+
"stale": [],
|
|
253
|
+
"staleCount": 0,
|
|
254
|
+
"staleApplicable": False,
|
|
255
|
+
"exportCoverage": round2(tally["complete"] / total * 100),
|
|
256
|
+
"incomplete": tally["incomplete"],
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
|
|
194
260
|
def reconcile(inp, doc_text=None):
|
|
195
261
|
"""Pure reconciliation. `doc_text` may be injected (tests); else loaded from disk."""
|
|
196
262
|
err = _validate(inp)
|
|
@@ -199,6 +265,9 @@ def reconcile(inp, doc_text=None):
|
|
|
199
265
|
|
|
200
266
|
source = inp["denominatorSource"]
|
|
201
267
|
|
|
268
|
+
if source == "docsOnly":
|
|
269
|
+
return _docs_only(inp)
|
|
270
|
+
|
|
202
271
|
if source == "barrel":
|
|
203
272
|
documented_set = build_documented_set(inp.get("exports"))
|
|
204
273
|
barrel_set = build_barrel_set(inp)
|
|
@@ -225,39 +294,43 @@ def reconcile(inp, doc_text=None):
|
|
|
225
294
|
"exportCoverage": round2(documented / denominator * 100),
|
|
226
295
|
}
|
|
227
296
|
|
|
228
|
-
# scalar / stack
|
|
297
|
+
# scalar / stack: looked-up numerator against SKILL.md ∪ references
|
|
229
298
|
denominator = inp["denominatorValue"]
|
|
230
|
-
if doc_text is None:
|
|
231
|
-
doc_text = load_doc_text(inp["skillPackagePath"])
|
|
232
|
-
|
|
233
|
-
if source == "scalar":
|
|
234
|
-
candidates = sorted(build_documented_set(inp.get("exports")))
|
|
235
|
-
else: # stack
|
|
236
|
-
candidates = [n for n in inp["compositionNames"] if isinstance(n, str) and n]
|
|
237
|
-
|
|
238
|
-
present = names_present(candidates, doc_text)
|
|
239
|
-
documented = len(present)
|
|
240
|
-
|
|
241
299
|
if denominator == 0:
|
|
242
300
|
return make_error(
|
|
243
301
|
f"{source} branch: denominatorValue is 0 — Export Coverage is undefined; "
|
|
244
302
|
"upstream §2b guard should HALT before reconciliation"
|
|
245
303
|
)
|
|
246
304
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
305
|
+
numerator_source = "lookup"
|
|
306
|
+
if source == "scalar" and inp.get("verifiedNumerator") is not None:
|
|
307
|
+
# §2b's numerator ground truth found the declared count padded: its
|
|
308
|
+
# verified count is authoritative and replaces the lookup.
|
|
309
|
+
documented = inp["verifiedNumerator"]
|
|
310
|
+
numerator_source = "verified"
|
|
311
|
+
else:
|
|
312
|
+
if doc_text is None:
|
|
313
|
+
doc_text = load_doc_text(inp["skillPackagePath"])
|
|
314
|
+
if source == "scalar":
|
|
315
|
+
candidates = sorted(build_documented_set(inp.get("exports")))
|
|
316
|
+
else: # stack
|
|
317
|
+
candidates = [n for n in inp["compositionNames"] if isinstance(n, str) and n]
|
|
318
|
+
documented = len(names_present(candidates, doc_text))
|
|
319
|
+
|
|
320
|
+
# The looked-up numerator and the resolved denominator are independent
|
|
321
|
+
# measures, so `documented` can legitimately exceed `denominator`: a
|
|
322
|
+
# consumer-surface denominator counts one surface while the documented body
|
|
323
|
+
# may also name migration aliases, re-exported sibling symbols, and other
|
|
324
|
+
# extras. Left unbounded that yields a negative Missing and >100% coverage
|
|
325
|
+
# (which compute-score.py then rejects outright as out of range).
|
|
253
326
|
#
|
|
254
|
-
# `documented` stays the true
|
|
327
|
+
# `documented` stays the true count: it is reported as "Documented in
|
|
255
328
|
# SKILL.md", so capping it would print a number that was never measured.
|
|
256
329
|
# The derived ratio is what gets bounded, and the surplus is surfaced rather
|
|
257
330
|
# than swallowed so a deflated denominator stays visible.
|
|
258
331
|
surplus = max(0, documented - denominator)
|
|
259
332
|
coverage_uncapped = round2(documented / denominator * 100)
|
|
260
|
-
|
|
333
|
+
result = {
|
|
261
334
|
"branch": source,
|
|
262
335
|
"denominatorSource": source,
|
|
263
336
|
"denominator": denominator,
|
|
@@ -272,6 +345,73 @@ def reconcile(inp, doc_text=None):
|
|
|
272
345
|
"coverageUncapped": coverage_uncapped,
|
|
273
346
|
"coverageCapped": surplus > 0,
|
|
274
347
|
}
|
|
348
|
+
if source == "scalar":
|
|
349
|
+
result["numeratorSource"] = numerator_source
|
|
350
|
+
return result
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# --- Run-folder inputs --------------------------------------------------------
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class InputFileError(Exception):
|
|
357
|
+
"""A file flag names a file that cannot be read or does not hold what it should."""
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _read_json_file(path, flag):
|
|
361
|
+
try:
|
|
362
|
+
return json.loads(Path(path).read_bytes().decode("utf-8-sig"))
|
|
363
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
364
|
+
raise InputFileError(f"cannot read {flag} {path}: {exc}") from exc
|
|
365
|
+
except json.JSONDecodeError as exc:
|
|
366
|
+
raise InputFileError(f"{flag} {path} is not JSON: {exc.msg}") from exc
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def apply_file_inputs(inp, args):
|
|
370
|
+
"""Fill the input object from the file flags; each flag wins over the JSON field."""
|
|
371
|
+
if args.denominator_source is not None:
|
|
372
|
+
inp["denominatorSource"] = args.denominator_source
|
|
373
|
+
if args.denominator_value is not None:
|
|
374
|
+
inp["denominatorValue"] = args.denominator_value
|
|
375
|
+
if args.skill_dir is not None:
|
|
376
|
+
inp["skillPackagePath"] = args.skill_dir
|
|
377
|
+
if args.inventory is not None:
|
|
378
|
+
data = _read_json_file(args.inventory, "--inventory")
|
|
379
|
+
if isinstance(data, dict) and isinstance(data.get("inventory"), dict):
|
|
380
|
+
data = data["inventory"] # validate-inventory.py's whole result
|
|
381
|
+
if not isinstance(data, dict) or not isinstance(data.get("exports"), list):
|
|
382
|
+
raise InputFileError(f"--inventory {args.inventory} holds no `exports` list")
|
|
383
|
+
inp["exports"] = data["exports"]
|
|
384
|
+
if args.surface is not None:
|
|
385
|
+
data = _read_json_file(args.surface, "--surface")
|
|
386
|
+
sets = data.get("sets") if isinstance(data, dict) else None
|
|
387
|
+
names = sets.get(args.surface_set) if isinstance(sets, dict) else None
|
|
388
|
+
if not isinstance(names, list):
|
|
389
|
+
raise InputFileError(f"--surface {args.surface} has no name set `{args.surface_set}`")
|
|
390
|
+
inp["barrelSet"] = names
|
|
391
|
+
if args.coverage_inputs is not None:
|
|
392
|
+
data = _read_json_file(args.coverage_inputs, "--coverage-inputs")
|
|
393
|
+
if not isinstance(data, dict):
|
|
394
|
+
raise InputFileError(f"--coverage-inputs {args.coverage_inputs} is not a JSON object")
|
|
395
|
+
if inp.get("denominatorSource") == "scalar":
|
|
396
|
+
if args.denominator_value is None:
|
|
397
|
+
if data.get("effectiveDenominator") is None:
|
|
398
|
+
raise InputFileError(f"--coverage-inputs {args.coverage_inputs} has no "
|
|
399
|
+
"`effectiveDenominator`: metadata.json has no stats.effective_denominator")
|
|
400
|
+
inp["denominatorValue"] = data["effectiveDenominator"]
|
|
401
|
+
else:
|
|
402
|
+
stack = data.get("stack")
|
|
403
|
+
if not isinstance(stack, dict):
|
|
404
|
+
raise InputFileError(f"--coverage-inputs {args.coverage_inputs} has no `stack` object")
|
|
405
|
+
inp["compositionNames"] = stack.get("compositionNames")
|
|
406
|
+
if args.denominator_value is None:
|
|
407
|
+
inp["denominatorValue"] = stack.get("denominator")
|
|
408
|
+
if args.verified is not None:
|
|
409
|
+
data = _read_json_file(args.verified, "--verified")
|
|
410
|
+
if not isinstance(data, dict):
|
|
411
|
+
raise InputFileError(f"--verified {args.verified} is not a verify-declared-numerator.py result")
|
|
412
|
+
if data.get("inflated") is True:
|
|
413
|
+
inp["verifiedNumerator"] = data.get("verified")
|
|
414
|
+
return inp
|
|
275
415
|
|
|
276
416
|
|
|
277
417
|
# --- CLI --------------------------------------------------------------------
|
|
@@ -283,17 +423,16 @@ def _build_parser():
|
|
|
283
423
|
description=(
|
|
284
424
|
"Deterministic Documented-vs-Source coverage reconciliation "
|
|
285
425
|
"(coverage-check.md §2c). Consumes the §1 documented inventory and "
|
|
286
|
-
"the §2 source
|
|
287
|
-
"skill package path
|
|
288
|
-
"exportCoverage} so the coverage
|
|
426
|
+
"the §2 source surface (or a resolved scalar/stack denominator + the "
|
|
427
|
+
"skill package path, or the inventory alone for a docs-only skill) and "
|
|
428
|
+
"emits {documented, missing, stale, exportCoverage} so the coverage "
|
|
429
|
+
"numerator is reproducible."
|
|
289
430
|
),
|
|
290
431
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
291
432
|
epilog=(
|
|
292
433
|
"Example (barrel branch):\n"
|
|
293
|
-
" uv run reconcile-coverage.py "
|
|
294
|
-
"
|
|
295
|
-
"\"exports\":[{\"name\":\"a\",\"kind\":\"function\"}],"
|
|
296
|
-
"\"barrelSet\":[\"a\",\"b\"]}'"
|
|
434
|
+
" uv run reconcile-coverage.py --denominator-source barrel "
|
|
435
|
+
"--inventory run/inventory.json --surface run/surface.json --output run/coverage.json"
|
|
297
436
|
),
|
|
298
437
|
)
|
|
299
438
|
src = parser.add_mutually_exclusive_group()
|
|
@@ -312,9 +451,27 @@ def _build_parser():
|
|
|
312
451
|
action="store_true",
|
|
313
452
|
help="Read the JSON object from stdin.",
|
|
314
453
|
)
|
|
454
|
+
files = parser.add_argument_group("inputs read from files (each wins over the same JSON field)")
|
|
455
|
+
files.add_argument("--denominator-source", choices=VALID_SOURCES, help="the §2c branch")
|
|
456
|
+
files.add_argument("--denominator-value", type=int, metavar="N",
|
|
457
|
+
help="the resolved scalar denominator")
|
|
458
|
+
files.add_argument("--skill-dir", metavar="DIR", help="the skill package: SKILL.md and references/")
|
|
459
|
+
files.add_argument("--inventory", metavar="PATH", help="the validated inventory file")
|
|
460
|
+
files.add_argument("--surface", metavar="PATH", help="load-coverage-inputs.py surface output")
|
|
461
|
+
files.add_argument("--surface-set", default="all", metavar="NAME",
|
|
462
|
+
help="the name set of --surface to intersect against (default: all)")
|
|
463
|
+
files.add_argument("--coverage-inputs", metavar="PATH",
|
|
464
|
+
help="load-coverage-inputs.py metadata output (scalar and stack branches)")
|
|
465
|
+
files.add_argument("--verified", metavar="PATH",
|
|
466
|
+
help="verify-declared-numerator.py output (scalar branch)")
|
|
467
|
+
parser.add_argument("--output", metavar="PATH", help="also write the result to this file")
|
|
315
468
|
return parser
|
|
316
469
|
|
|
317
470
|
|
|
471
|
+
FILE_FLAGS = ("denominator_source", "denominator_value", "skill_dir", "inventory", "surface",
|
|
472
|
+
"coverage_inputs", "verified")
|
|
473
|
+
|
|
474
|
+
|
|
318
475
|
def _resolve_input(args):
|
|
319
476
|
if args.stdin:
|
|
320
477
|
return sys.stdin.read()
|
|
@@ -325,30 +482,67 @@ def _resolve_input(args):
|
|
|
325
482
|
return ""
|
|
326
483
|
|
|
327
484
|
|
|
485
|
+
def _write_output(path, result):
|
|
486
|
+
target = Path(path)
|
|
487
|
+
if result.get("code") == "INVALID_INPUT":
|
|
488
|
+
target.unlink(missing_ok=True)
|
|
489
|
+
return
|
|
490
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
491
|
+
target.write_bytes((json.dumps(result, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
|
|
492
|
+
|
|
493
|
+
|
|
328
494
|
def main(argv=None):
|
|
329
495
|
parser = _build_parser()
|
|
330
496
|
args = parser.parse_args(argv)
|
|
331
497
|
raw = _resolve_input(args)
|
|
332
|
-
|
|
498
|
+
from_files = any(getattr(args, flag) is not None for flag in FILE_FLAGS)
|
|
499
|
+
if not raw.strip() and not from_files:
|
|
333
500
|
parser.print_usage(file=sys.stderr)
|
|
334
501
|
print(
|
|
335
|
-
"error: no input provided (positional arg, --json-input, or
|
|
502
|
+
"error: no input provided (positional arg, --json-input, --stdin or the file flags)",
|
|
336
503
|
file=sys.stderr,
|
|
337
504
|
)
|
|
338
505
|
return 1
|
|
339
506
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
507
|
+
data = {}
|
|
508
|
+
if raw.strip():
|
|
509
|
+
try:
|
|
510
|
+
data = json.loads(raw)
|
|
511
|
+
except json.JSONDecodeError as exc:
|
|
512
|
+
print(json.dumps(make_error(f"Invalid JSON: {exc.msg}"), indent=2))
|
|
513
|
+
return 1
|
|
514
|
+
|
|
515
|
+
if isinstance(data, dict):
|
|
516
|
+
try:
|
|
517
|
+
data = apply_file_inputs(data, args)
|
|
518
|
+
except InputFileError as exc:
|
|
519
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
520
|
+
return 1
|
|
345
521
|
|
|
346
522
|
result = reconcile(data)
|
|
523
|
+
if args.output:
|
|
524
|
+
_write_output(args.output, result)
|
|
347
525
|
print(json.dumps(result, indent=2))
|
|
348
526
|
if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
|
|
349
527
|
return 2
|
|
350
528
|
return 0
|
|
351
529
|
|
|
352
530
|
|
|
531
|
+
def _force_utf8(*streams) -> None:
|
|
532
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
533
|
+
|
|
534
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
535
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
536
|
+
"""
|
|
537
|
+
for stream in streams:
|
|
538
|
+
if hasattr(stream, "reconfigure"):
|
|
539
|
+
errors = getattr(stream, "errors", None)
|
|
540
|
+
if errors is None:
|
|
541
|
+
stream.reconfigure(encoding="utf-8")
|
|
542
|
+
else:
|
|
543
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
544
|
+
|
|
545
|
+
|
|
353
546
|
if __name__ == "__main__":
|
|
547
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
354
548
|
raise SystemExit(main())
|