bmad-module-skill-forge 2.2.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +143 -18
- package/docs/verifying-a-skill.md +87 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +3 -3
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +31 -59
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +233 -64
- package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +772 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +21 -3
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +197 -55
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +5247 -96
- package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +664 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +469 -205
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +700 -0
- package/src/shared/scripts/skf-hash-content.py +180 -20
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +161 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +143 -20
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +176 -28
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +155 -14
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +787 -121
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +120 -67
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +23 -2
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +3945 -152
- package/src/shared/scripts/skf-write-skill-brief.py +406 -25
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +28 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/health-check.md +2 -0
- package/src/skf-audit-skill/references/init.md +149 -210
- package/src/skf-audit-skill/references/re-index.md +103 -110
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +34 -25
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +80 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
- package/src/skf-brief-skill/SKILL.md +37 -26
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +18 -25
- package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +144 -127
- package/src/skf-brief-skill/references/confirm-brief.md +11 -9
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +7 -10
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
- package/src/skf-create-skill/assets/skill-sections.md +21 -11
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +119 -70
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
- package/src/skf-create-skill/references/extract.md +204 -162
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
- package/src/skf-create-skill/references/extraction-patterns.md +588 -201
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +37 -57
- package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +83 -146
- package/src/skf-drop-skill/references/health-check.md +2 -0
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +122 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +209 -264
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +164 -91
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +164 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +42 -155
- package/src/skf-test-skill/references/source-access-protocol.md +18 -94
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +380 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
- package/src/skf-test-skill/scripts/score-signatures.py +479 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +253 -223
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +254 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +182 -49
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +102 -35
- package/src/skf-update-skill/references/write.md +357 -155
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -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 schema validation of the §1 subagent inventory JSON.
|
|
6
6
|
|
|
@@ -13,19 +13,58 @@ and types, assert each export entry is a dict with a non-empty `name` and a
|
|
|
13
13
|
`kind` drawn from a fixed enum, and assert each cross-check mismatch carries its
|
|
14
14
|
five fields — with exactly one correct pass/fail per input and no
|
|
15
15
|
interpretation of meaning. It is therefore script work, not prompt work; the
|
|
16
|
-
prompt reads this verdict and owns the HALT decision
|
|
17
|
-
|
|
16
|
+
prompt reads this verdict and owns only the HALT decision.
|
|
17
|
+
|
|
18
|
+
With --skill-package it also runs the ground-truth spot-check (coverage-check.md
|
|
19
|
+
§1a): it sorts the exports by name (a stable sort), samples the first, the
|
|
20
|
+
middle and the last (`min(3, exportsCount)` names, indices
|
|
21
|
+
`[0, len//2, len-1]`), and looks each sampled name up in the package's
|
|
22
|
+
SKILL.md and in every file the inventory's `references` list names, with
|
|
23
|
+
the documented-name match below. A sampled name none of them writes is a
|
|
24
|
+
fabricated export: the inventory is then invalid, each absent name is a
|
|
25
|
+
violation, and the prompt re-dispatches the subagent with them, as it does
|
|
26
|
+
for a schema violation. No grep runs in the prompt, so an export named
|
|
27
|
+
`$state` or `a.b` is never a shell variable or a regex.
|
|
18
28
|
|
|
19
29
|
Distinct from reconcile-coverage.py (coverage numerator arithmetic) and
|
|
20
30
|
compute-score.py (weight tables): this script only validates the *shape* of the
|
|
21
31
|
subagent inventory and, on success, echoes the fence-stripped parsed inventory
|
|
22
32
|
back so the prompt consumes it directly instead of re-parsing the raw response.
|
|
23
33
|
|
|
34
|
+
It also reports each documented item's completeness, the docs-only Export
|
|
35
|
+
Coverage rule: an item (one name and kind) is complete when it has a
|
|
36
|
+
non-empty `description` and, for a function or a method, `params` (a string
|
|
37
|
+
or a list, empty when the function takes none) and a non-empty
|
|
38
|
+
`return_type`. A missing description makes an item incomplete; it never
|
|
39
|
+
makes the inventory invalid. reconcile-coverage.py's docsOnly branch scores
|
|
40
|
+
with the same rule.
|
|
41
|
+
|
|
42
|
+
Shared helpers. The other coverage scripts load these from this file rather
|
|
43
|
+
than keep copies: `strip_fences` (score-signatures.py), `completeness`
|
|
44
|
+
(reconcile-coverage.py) and the documented-name match, `load_doc_text` and
|
|
45
|
+
`names_present` (reconcile-coverage.py, verify-declared-numerator.py,
|
|
46
|
+
load-coverage-inputs.py). A name is documented when SKILL.md or a .md file
|
|
47
|
+
directly in references/ writes it as a fixed string, case-sensitive, never
|
|
48
|
+
inside a longer identifier (the Export match rule of
|
|
49
|
+
skf-scan-skill-md-structure.py usage-scope): on a side where the name ends
|
|
50
|
+
in a letter, digit, `_` or `$`, the next character may not be one of those.
|
|
51
|
+
So `get` is found in `get(url)` but not in `target` or `getAll`.
|
|
52
|
+
|
|
24
53
|
CLI usage (mirrors reconcile-coverage.py):
|
|
54
|
+
uv run validate-inventory.py --input <response file> [--output <inventory file>]
|
|
55
|
+
[--skill-package <package folder>]
|
|
25
56
|
uv run validate-inventory.py '<raw response>' # positional
|
|
26
57
|
uv run validate-inventory.py --json-input '<raw response>' # explicit flag
|
|
27
58
|
echo '<raw response>' | uv run validate-inventory.py --stdin # piped input
|
|
28
59
|
|
|
60
|
+
--input reads the raw response from a file (UTF-8): coverage-check.md saves
|
|
61
|
+
the subagent's response with the Write tool and passes its path, so a quote
|
|
62
|
+
or an apostrophe in a description never meets a shell. --output writes the
|
|
63
|
+
validated inventory object to a file (UTF-8 JSON) when it is valid, for
|
|
64
|
+
the steps that read it by path; an invalid inventory removes a file left
|
|
65
|
+
there by an earlier attempt. --skill-package names the skill package under
|
|
66
|
+
test (the folder that holds SKILL.md) and adds the spot-check.
|
|
67
|
+
|
|
29
68
|
Input: the subagent's RAW response text (JSON, optionally wrapped in a markdown
|
|
30
69
|
code fence — a leading line of three backticks with an optional language tag and
|
|
31
70
|
a trailing line of three backticks are stripped before parsing).
|
|
@@ -36,21 +75,36 @@ Output (stdout, one object):
|
|
|
36
75
|
"violations": [<str>, ...], # human-readable failures; empty when valid
|
|
37
76
|
"rejectedCount": <int>, # count of malformed exports[] entries
|
|
38
77
|
"exportsCount": <int>, # len(exports) when parsed; 0 otherwise
|
|
39
|
-
"inventory": {...} | null
|
|
78
|
+
"inventory": {...} | null, # fence-stripped parsed inventory when valid; null otherwise
|
|
79
|
+
"completeness": { # present when valid
|
|
80
|
+
"total": <int>, # documented items, one per name and kind
|
|
81
|
+
"complete": <int>,
|
|
82
|
+
"incomplete": [{"name", "kind", "missing": ["description", "params", "return_type"]}]
|
|
83
|
+
},
|
|
84
|
+
"spotCheck": { # with --skill-package, once the schema passed
|
|
85
|
+
"sampled": [<name>, ...], # the sampled names, in sample order
|
|
86
|
+
"absent": [<name>, ...], # the sampled names no file writes
|
|
87
|
+
"files": [<path>, ...], # the files read, relative to the package
|
|
88
|
+
"unread": [<path>, ...] # listed references that are missing,
|
|
89
|
+
} # unreadable or outside the package
|
|
40
90
|
}
|
|
41
91
|
|
|
42
92
|
Exit codes:
|
|
43
|
-
0
|
|
44
|
-
1
|
|
45
|
-
|
|
93
|
+
0 inventory valid (and every sampled name found)
|
|
94
|
+
1 no input provided, the --input file cannot be read, or the
|
|
95
|
+
--skill-package folder holds no readable SKILL.md (usage error)
|
|
96
|
+
2 inventory invalid (parse failure, schema violation or a sampled name
|
|
97
|
+
absent; result JSON emitted)
|
|
46
98
|
"""
|
|
47
99
|
|
|
48
100
|
from __future__ import annotations
|
|
49
101
|
|
|
50
102
|
import argparse
|
|
51
103
|
import json
|
|
104
|
+
import os
|
|
52
105
|
import re
|
|
53
106
|
import sys
|
|
107
|
+
from pathlib import Path
|
|
54
108
|
|
|
55
109
|
# Fixed enum for exports[].kind — the constructs SKF documents across languages
|
|
56
110
|
# and skill types: JS/TS (function/class/type/constant/hook/interface/method),
|
|
@@ -80,6 +134,15 @@ REQUIRED_MISMATCH_FIELDS = (
|
|
|
80
134
|
"issue",
|
|
81
135
|
)
|
|
82
136
|
|
|
137
|
+
# Kinds whose documented item also needs its parameters and return type to
|
|
138
|
+
# be complete (the docs-only Export Coverage rule).
|
|
139
|
+
SIGNATURE_KINDS = ("function", "method")
|
|
140
|
+
|
|
141
|
+
# Characters that continue an identifier in the languages SKF documents
|
|
142
|
+
# (`$` for JS/TS names such as `$state`).
|
|
143
|
+
IDENT_CHARS = r"\w$"
|
|
144
|
+
_IDENT_RE = re.compile(f"[{IDENT_CHARS}]")
|
|
145
|
+
|
|
83
146
|
_FENCE_OPEN = re.compile(r"^```[A-Za-z0-9_-]*$")
|
|
84
147
|
_FENCE_CLOSE = re.compile(r"^```$")
|
|
85
148
|
|
|
@@ -102,11 +165,174 @@ def strip_fences(text):
|
|
|
102
165
|
return text
|
|
103
166
|
|
|
104
167
|
|
|
105
|
-
|
|
106
|
-
|
|
168
|
+
# --- Documented-name match --------------------------------------------------
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def name_pattern(name):
|
|
172
|
+
"""Regex for `name` as a fixed string that is never part of a longer identifier.
|
|
173
|
+
|
|
174
|
+
The guard only applies on a side where the name itself ends in an
|
|
175
|
+
identifier character, so `.then` still matches inside `promise.then` and
|
|
176
|
+
`Type::method` matches as written.
|
|
177
|
+
"""
|
|
178
|
+
body = re.escape(name)
|
|
179
|
+
if _IDENT_RE.match(name[0]):
|
|
180
|
+
body = f"(?<![{IDENT_CHARS}])" + body
|
|
181
|
+
if _IDENT_RE.match(name[-1]):
|
|
182
|
+
body += f"(?![{IDENT_CHARS}])"
|
|
183
|
+
return re.compile(body)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def load_doc_text(skill_package_path):
|
|
187
|
+
"""Concatenate SKILL.md ∪ references/*.md text for name lookups.
|
|
188
|
+
|
|
189
|
+
Deterministic: references are read in sorted order, a references/
|
|
190
|
+
subfolder does not count. Reads as UTF-8 so non-ASCII exports do not
|
|
191
|
+
mojibake on Windows (cp1252 default).
|
|
192
|
+
"""
|
|
193
|
+
root = Path(skill_package_path)
|
|
194
|
+
parts = []
|
|
195
|
+
skill_md = root / "SKILL.md"
|
|
196
|
+
if skill_md.is_file():
|
|
197
|
+
parts.append(skill_md.read_text(encoding="utf-8"))
|
|
198
|
+
refs_dir = root / "references"
|
|
199
|
+
if refs_dir.is_dir():
|
|
200
|
+
for ref in sorted(refs_dir.glob("*.md")):
|
|
201
|
+
parts.append(ref.read_text(encoding="utf-8"))
|
|
202
|
+
return "\n".join(parts)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def names_present(names, doc_text):
|
|
206
|
+
"""Return the sorted, de-duplicated set of `names` the doc text writes."""
|
|
207
|
+
present = {n for n in names if isinstance(n, str) and n and name_pattern(n).search(doc_text)}
|
|
208
|
+
return sorted(present)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# --- Spot-check -------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def sample_names(exports):
|
|
215
|
+
"""The names the spot-check samples: first, middle and last after a stable sort by name.
|
|
216
|
+
|
|
217
|
+
>>> sample_names([{"name": n} for n in ("d", "a", "c", "b", "e")])
|
|
218
|
+
['a', 'c', 'e']
|
|
219
|
+
>>> sample_names([{"name": "b"}, {"name": "a"}])
|
|
220
|
+
['a', 'b']
|
|
221
|
+
>>> sample_names([])
|
|
222
|
+
[]
|
|
223
|
+
"""
|
|
224
|
+
named = sorted((e["name"] for e in exports or []
|
|
225
|
+
if isinstance(e, dict) and isinstance(e.get("name"), str) and e["name"]))
|
|
226
|
+
picks = []
|
|
227
|
+
for index in (0, len(named) // 2, len(named) - 1):
|
|
228
|
+
if named and named[index] not in picks:
|
|
229
|
+
picks.append(named[index])
|
|
230
|
+
return picks
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class PackageError(Exception):
|
|
234
|
+
"""The --skill-package folder holds no readable SKILL.md: exit 1."""
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _inside(path, root):
|
|
238
|
+
path, root = os.path.normcase(path), os.path.normcase(root)
|
|
239
|
+
return path == root or path.startswith(root.rstrip(os.sep) + os.sep)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def spot_check(inventory, skill_package_path):
|
|
243
|
+
"""Look the sampled export names up in SKILL.md and the inventory's references.
|
|
244
|
+
|
|
245
|
+
The file set is the package's SKILL.md plus each `references` entry,
|
|
246
|
+
resolved against the package (a split-body skill documents some exports
|
|
247
|
+
only in a reference file). A listed file that is missing, unreadable or
|
|
248
|
+
resolves outside the package is not read and is named in `unread`.
|
|
249
|
+
"""
|
|
250
|
+
root = Path(skill_package_path)
|
|
251
|
+
real_root = os.path.realpath(root)
|
|
252
|
+
try:
|
|
253
|
+
texts = [(root / "SKILL.md").read_bytes().decode("utf-8-sig")]
|
|
254
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
255
|
+
raise PackageError(f"cannot read {(root / 'SKILL.md').as_posix()}: {exc}") from exc
|
|
256
|
+
files, unread = ["SKILL.md"], []
|
|
257
|
+
listed = inventory.get("references")
|
|
258
|
+
for ref in listed if isinstance(listed, list) else []:
|
|
259
|
+
if not isinstance(ref, str) or not ref.strip() or ref in files or ref in unread:
|
|
260
|
+
continue
|
|
261
|
+
target = os.path.realpath(root / ref)
|
|
262
|
+
try:
|
|
263
|
+
if not _inside(target, real_root):
|
|
264
|
+
raise OSError("outside the package")
|
|
265
|
+
texts.append(Path(target).read_bytes().decode("utf-8-sig"))
|
|
266
|
+
files.append(ref)
|
|
267
|
+
except (OSError, UnicodeDecodeError):
|
|
268
|
+
unread.append(ref)
|
|
269
|
+
doc_text = "\n".join(texts)
|
|
270
|
+
sampled = sample_names(inventory.get("exports"))
|
|
271
|
+
found = set(names_present(sampled, doc_text))
|
|
272
|
+
return {
|
|
273
|
+
"sampled": sampled,
|
|
274
|
+
"absent": [name for name in sampled if name not in found],
|
|
275
|
+
"files": files,
|
|
276
|
+
"unread": unread,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
# --- Completeness -------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _filled(value):
|
|
284
|
+
return isinstance(value, str) and bool(value.strip())
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def missing_fields(entry):
|
|
288
|
+
"""The fields a documented item lacks to be complete (docs-only rule)."""
|
|
289
|
+
missing = []
|
|
290
|
+
if not _filled(entry.get("description")):
|
|
291
|
+
missing.append("description")
|
|
292
|
+
if entry.get("kind") in SIGNATURE_KINDS:
|
|
293
|
+
if not isinstance(entry.get("params"), (str, list)):
|
|
294
|
+
missing.append("params")
|
|
295
|
+
if not _filled(entry.get("return_type")):
|
|
296
|
+
missing.append("return_type")
|
|
297
|
+
return missing
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def completeness(exports):
|
|
301
|
+
"""Count complete documented items: one item per name and kind.
|
|
302
|
+
|
|
303
|
+
An item the inventory lists twice is complete when either entry is.
|
|
304
|
+
Entries without a string name or a known kind are not items.
|
|
305
|
+
"""
|
|
306
|
+
items = {}
|
|
307
|
+
for entry in exports or []:
|
|
308
|
+
if not isinstance(entry, dict):
|
|
309
|
+
continue
|
|
310
|
+
name, kind = entry.get("name"), entry.get("kind")
|
|
311
|
+
if not (isinstance(name, str) and name) or kind not in VALID_KINDS:
|
|
312
|
+
continue
|
|
313
|
+
missing = missing_fields(entry)
|
|
314
|
+
key = (name, kind)
|
|
315
|
+
if key not in items or not missing:
|
|
316
|
+
items[key] = missing
|
|
317
|
+
incomplete = [{"name": name, "kind": kind, "missing": missing}
|
|
318
|
+
for (name, kind), missing in items.items() if missing]
|
|
319
|
+
return {
|
|
320
|
+
"total": len(items),
|
|
321
|
+
"complete": len(items) - len(incomplete),
|
|
322
|
+
"incomplete": incomplete,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# --- Validation ---------------------------------------------------------------
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def validate_inventory(raw, skill_package_path=None):
|
|
330
|
+
"""Structural validation of the subagent inventory response, then the spot-check.
|
|
107
331
|
|
|
108
332
|
`raw` is the raw response text. Returns the result dict documented in the
|
|
109
|
-
module docstring.
|
|
333
|
+
module docstring. With `skill_package_path`, an inventory that passed the
|
|
334
|
+
schema is spot-checked against the package (spot_check). Deterministic:
|
|
335
|
+
identical input yields an identical verdict.
|
|
110
336
|
"""
|
|
111
337
|
violations = []
|
|
112
338
|
inner = strip_fences(raw)
|
|
@@ -177,14 +403,28 @@ def validate_inventory(raw):
|
|
|
177
403
|
f"cross_check_mismatches[{idx}] missing field(s): {', '.join(missing)}"
|
|
178
404
|
)
|
|
179
405
|
|
|
406
|
+
checked = None
|
|
407
|
+
if not violations and skill_package_path is not None:
|
|
408
|
+
checked = spot_check(data, skill_package_path)
|
|
409
|
+
for name in checked["absent"]:
|
|
410
|
+
violations.append(
|
|
411
|
+
f"spot-check: `{name}` is listed as an export, but SKILL.md and the "
|
|
412
|
+
f"listed reference files never write it"
|
|
413
|
+
)
|
|
414
|
+
|
|
180
415
|
valid = not violations
|
|
181
|
-
|
|
416
|
+
result = {
|
|
182
417
|
"valid": valid,
|
|
183
418
|
"violations": violations,
|
|
184
419
|
"rejectedCount": rejected,
|
|
185
420
|
"exportsCount": len(exports),
|
|
186
421
|
"inventory": data if valid else None,
|
|
187
422
|
}
|
|
423
|
+
if valid:
|
|
424
|
+
result["completeness"] = completeness(exports)
|
|
425
|
+
if checked is not None:
|
|
426
|
+
result["spotCheck"] = checked
|
|
427
|
+
return result
|
|
188
428
|
|
|
189
429
|
|
|
190
430
|
# --- CLI --------------------------------------------------------------------
|
|
@@ -196,8 +436,9 @@ def _build_parser():
|
|
|
196
436
|
description=(
|
|
197
437
|
"Deterministic schema validation of the §1 subagent inventory JSON "
|
|
198
438
|
"(coverage-check.md §1a). Strips wrapping fences, parses, and asserts "
|
|
199
|
-
"the required-keys/types/enum/mismatch-field contract,
|
|
200
|
-
"
|
|
439
|
+
"the required-keys/types/enum/mismatch-field contract, then with "
|
|
440
|
+
"--skill-package spot-checks sampled names against the package, returning "
|
|
441
|
+
"{valid, violations, rejectedCount, exportsCount, inventory, completeness, spotCheck}."
|
|
201
442
|
),
|
|
202
443
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
203
444
|
)
|
|
@@ -217,12 +458,30 @@ def _build_parser():
|
|
|
217
458
|
action="store_true",
|
|
218
459
|
help="Read the raw subagent response from stdin.",
|
|
219
460
|
)
|
|
461
|
+
src.add_argument(
|
|
462
|
+
"--input",
|
|
463
|
+
dest="input_file",
|
|
464
|
+
metavar="PATH",
|
|
465
|
+
help="Read the raw subagent response from this file (UTF-8).",
|
|
466
|
+
)
|
|
467
|
+
parser.add_argument(
|
|
468
|
+
"--output",
|
|
469
|
+
metavar="PATH",
|
|
470
|
+
help="Write the validated inventory to this file when it is valid.",
|
|
471
|
+
)
|
|
472
|
+
parser.add_argument(
|
|
473
|
+
"--skill-package",
|
|
474
|
+
metavar="DIR",
|
|
475
|
+
help="Spot-check sampled names against this package's SKILL.md and listed references.",
|
|
476
|
+
)
|
|
220
477
|
return parser
|
|
221
478
|
|
|
222
479
|
|
|
223
480
|
def _resolve_input(args):
|
|
224
481
|
if args.stdin:
|
|
225
482
|
return sys.stdin.read()
|
|
483
|
+
if args.input_file is not None:
|
|
484
|
+
return Path(args.input_file).read_bytes().decode("utf-8-sig")
|
|
226
485
|
if args.raw_input_flag is not None:
|
|
227
486
|
return args.raw_input_flag
|
|
228
487
|
if args.raw_input is not None:
|
|
@@ -230,22 +489,58 @@ def _resolve_input(args):
|
|
|
230
489
|
return ""
|
|
231
490
|
|
|
232
491
|
|
|
492
|
+
def write_json(path, payload):
|
|
493
|
+
"""Write `payload` as indented UTF-8 JSON with a final newline."""
|
|
494
|
+
target = Path(path)
|
|
495
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
496
|
+
target.write_bytes((json.dumps(payload, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
|
|
497
|
+
|
|
498
|
+
|
|
233
499
|
def main(argv=None):
|
|
234
500
|
parser = _build_parser()
|
|
235
501
|
args = parser.parse_args(argv)
|
|
236
|
-
|
|
502
|
+
try:
|
|
503
|
+
raw = _resolve_input(args)
|
|
504
|
+
except (OSError, UnicodeDecodeError) as exc:
|
|
505
|
+
print(f"error: cannot read --input {args.input_file}: {exc}", file=sys.stderr)
|
|
506
|
+
return 1
|
|
237
507
|
if not raw.strip():
|
|
238
508
|
parser.print_usage(file=sys.stderr)
|
|
239
509
|
print(
|
|
240
|
-
"error: no input provided (positional arg, --json-input, or --
|
|
510
|
+
"error: no input provided (positional arg, --json-input, --stdin or --input)",
|
|
241
511
|
file=sys.stderr,
|
|
242
512
|
)
|
|
243
513
|
return 1
|
|
244
514
|
|
|
245
|
-
|
|
515
|
+
try:
|
|
516
|
+
result = validate_inventory(raw, args.skill_package)
|
|
517
|
+
except PackageError as exc:
|
|
518
|
+
print(f"error: --skill-package: {exc}", file=sys.stderr)
|
|
519
|
+
return 1
|
|
520
|
+
if args.output:
|
|
521
|
+
if result["valid"]:
|
|
522
|
+
write_json(args.output, result["inventory"])
|
|
523
|
+
else:
|
|
524
|
+
Path(args.output).unlink(missing_ok=True)
|
|
246
525
|
print(json.dumps(result, indent=2))
|
|
247
526
|
return 0 if result["valid"] else 2
|
|
248
527
|
|
|
249
528
|
|
|
529
|
+
def _force_utf8(*streams) -> None:
|
|
530
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
531
|
+
|
|
532
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
533
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
534
|
+
"""
|
|
535
|
+
for stream in streams:
|
|
536
|
+
if hasattr(stream, "reconfigure"):
|
|
537
|
+
errors = getattr(stream, "errors", None)
|
|
538
|
+
if errors is None:
|
|
539
|
+
stream.reconfigure(encoding="utf-8")
|
|
540
|
+
else:
|
|
541
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
542
|
+
|
|
543
|
+
|
|
250
544
|
if __name__ == "__main__":
|
|
545
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
251
546
|
raise SystemExit(main())
|