bmad-module-skill-forge 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +51 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +14 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +215 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +17 -6
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -645
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
# /// script
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
|
+
# dependencies = []
|
|
4
|
+
# ///
|
|
5
|
+
"""SKF Extraction Snapshot: audit-skill's current export inventory, built by script.
|
|
6
|
+
|
|
7
|
+
audit-skill's step 2 (re-index.md) re-extracts the files the provenance map
|
|
8
|
+
covers and writes `extraction-snapshot.json`, which step 3 diffs against the
|
|
9
|
+
map. The recipe runner (skf-extract-public-api.py --mode full) does the
|
|
10
|
+
extraction; this helper gives it the files to read and turns its JSON, with
|
|
11
|
+
the exports the step read by eye, into the snapshot, so no step opens the
|
|
12
|
+
runner's JSON, copies hundreds of exports by hand or decides by eye whether
|
|
13
|
+
every file was read.
|
|
14
|
+
|
|
15
|
+
CLI:
|
|
16
|
+
|
|
17
|
+
uv run skf-extraction-snapshot.py scan-list <provenance-map> -o <files.json>
|
|
18
|
+
uv run skf-extraction-snapshot.py build --source-root <dir> --tier <tier> \\
|
|
19
|
+
--date <timestamp> [--source-path <recorded root>] \\
|
|
20
|
+
[--provenance-map <map>] [--extraction <runner.json>] \\
|
|
21
|
+
[--details <by-eye.json>]... -o <extraction-snapshot.json>
|
|
22
|
+
uv run skf-extraction-snapshot.py relocate <extraction-snapshot.json> \\
|
|
23
|
+
--diff <structural-diff.json> --extraction <runner.json>
|
|
24
|
+
|
|
25
|
+
scan-list
|
|
26
|
+
|
|
27
|
+
Writes the bounded scan list, the files the provenance map covers (the
|
|
28
|
+
union of `entries[].source_file` and `file_entries[].source_file`, sorted,
|
|
29
|
+
with forward slashes: skf-load-provenance.py's `bounded_scan_files`), as a
|
|
30
|
+
JSON list of paths, the form the runner's --files-from reads. Prints
|
|
31
|
+
{"status": "ok", "output": <file>, "files": N}.
|
|
32
|
+
|
|
33
|
+
build
|
|
34
|
+
|
|
35
|
+
The files in scope are the bounded scan list with --provenance-map (every
|
|
36
|
+
audit passes it), else every file the runner or the details name. Each
|
|
37
|
+
gets one status, in this order of precedence:
|
|
38
|
+
|
|
39
|
+
missing not a file under --source-root
|
|
40
|
+
hash-tracked only `file_entries[]` names it (a script, an asset or a
|
|
41
|
+
promoted doc): step 3 compares its content hash, so
|
|
42
|
+
nothing reads it for exports
|
|
43
|
+
read-by-eye the details name it in `files` (the step read it)
|
|
44
|
+
parse-failed the runner listed it in `file_issues` (a syntax error,
|
|
45
|
+
not UTF-8, unreadable, or a language no recipe reads):
|
|
46
|
+
read it by eye
|
|
47
|
+
unread no runner result, a runner that could not run
|
|
48
|
+
(`no-ast-grep`), or an incomplete run that names it as
|
|
49
|
+
unread (`errors[].unread`) or found no export in it
|
|
50
|
+
extracted the runner read it
|
|
51
|
+
|
|
52
|
+
`complete` is true when no file is `parse-failed` or `unread`: step 2
|
|
53
|
+
reads those by eye, adds them to the details and builds again. A file
|
|
54
|
+
carries `fallback: true` when the runner ran and left it `parse-failed`
|
|
55
|
+
or `unread`: read by eye after an ast-grep failure.
|
|
56
|
+
|
|
57
|
+
Exports: the runner's exports of the files in scope (each T1, with the
|
|
58
|
+
declaration on one line as `signature`), then each export of the details
|
|
59
|
+
(`name` or `export_name`, `file` or `source_file`, `line` or
|
|
60
|
+
`source_line`, `type` or `export_type`, `signature`; T1-low and
|
|
61
|
+
`source-read` unless it says otherwise). A details export with the name
|
|
62
|
+
and file of a runner export fills only the fields that one lacks; any
|
|
63
|
+
other is added. An export of a file outside the files in scope, or of a
|
|
64
|
+
`hash-tracked` one (the map records no export there, so it would read
|
|
65
|
+
as added), is left out, with a warning. With --provenance-map, each export of a file the
|
|
66
|
+
map gives a library (`source_library_by_file`) carries it as
|
|
67
|
+
`source_library`, which step 3's `--group-by source_library` diff reads.
|
|
68
|
+
|
|
69
|
+
Details file: {"files": [paths read by eye], "exports": [export objects]}
|
|
70
|
+
(either key may be absent). Give --details once per file: one per worker.
|
|
71
|
+
|
|
72
|
+
Output (-o), the snapshot step 3 reads:
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
"extraction_date": "<--date>",
|
|
76
|
+
"confidence_tier": "<--tier>",
|
|
77
|
+
"source_root": "<--source-path, else --source-root>",
|
|
78
|
+
"bounded_scan": true | false,
|
|
79
|
+
"bounded_scan_source": "provenance-map" | "source-tree-fallback",
|
|
80
|
+
"runner_status": "ok" | "incomplete" | "no-ast-grep" | null,
|
|
81
|
+
"runner_errors": [the runner's errors[]],
|
|
82
|
+
"files_scanned": N,
|
|
83
|
+
"complete": bool,
|
|
84
|
+
"files": [{"file", "status", "issue": <the runner's issue> | null,
|
|
85
|
+
"fallback": bool, "exports": N}, ...],
|
|
86
|
+
"files_by_status": {"extracted": N, "read-by-eye": N,
|
|
87
|
+
"hash-tracked": N, "missing": N,
|
|
88
|
+
"parse-failed": N, "unread": N},
|
|
89
|
+
"ast_fallback_files": ["<file>", ...],
|
|
90
|
+
"extraction_gaps": [{"name", "file", "line", "entry"}, ...],
|
|
91
|
+
"truncated": bool, # a recipe hit the runner's head cap
|
|
92
|
+
"cap_hits": ["<recipe id>", ...],
|
|
93
|
+
"outside_scope": [{"path": "<file>", "names": [...],
|
|
94
|
+
"entries": [...]}, ...],
|
|
95
|
+
"counts": {"exports": N, "by_type": {"<type>": N, ...},
|
|
96
|
+
"t1": N, "t1_low": N},
|
|
97
|
+
"exports": [{"name", "type", "signature", "file", "line",
|
|
98
|
+
"confidence", "extraction_method", "ast_node_type",
|
|
99
|
+
"source_library"?}, ...],
|
|
100
|
+
"relocations": [{"name", "file", "line"}, ...], # relocate's
|
|
101
|
+
"warnings": ["..."]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
`ast_fallback_files` names the files read after an ast-grep failure (the
|
|
105
|
+
`fallback` ones) when the runner ran; when it did not, it is
|
|
106
|
+
["all files (ast-grep unavailable)"], and [] at the Quick tier.
|
|
107
|
+
`extraction_gaps` lists the names a package entry point exports that no
|
|
108
|
+
recipe found (the runner's `entry_point_diff.extraction_gaps`), less the
|
|
109
|
+
ones an export of the snapshot now holds: read them by eye.
|
|
110
|
+
`outside_scope` groups the runner's `entry_point_diff.outside_scope` by
|
|
111
|
+
the file that defines each name: public API a package entry point exports
|
|
112
|
+
from a file the skill does not cover, which the drift report lists under
|
|
113
|
+
Out-of-Scope New Public API for update-skill's scope reconciliation.
|
|
114
|
+
|
|
115
|
+
Prints {"status": "ok", "output", "runner_status", "complete",
|
|
116
|
+
"to_read": [{"file", "status", "issue"}, ...] (the `parse-failed` and
|
|
117
|
+
`unread` files), "extraction_gaps", "ast_fallback_files",
|
|
118
|
+
"files_by_status", "counts"} on stdout: what step 2 acts on, so it never
|
|
119
|
+
opens the runner's JSON.
|
|
120
|
+
|
|
121
|
+
relocate
|
|
122
|
+
|
|
123
|
+
Step 3's relocation check: --extraction is the runner's JSON over the
|
|
124
|
+
files that hold a name the diff reports removed, outside the bounded scan
|
|
125
|
+
list. Each export the runner found there under a name of the diff's
|
|
126
|
+
`removed[]`, in a file outside the snapshot's files in scope, joins the
|
|
127
|
+
snapshot's exports (T1, with the removed entry's `source_library` when it
|
|
128
|
+
has one) and `relocations`; the counts are recounted and the snapshot is
|
|
129
|
+
written back in place. Prints {"status": "ok", "output", "added": N,
|
|
130
|
+
"names": [...]}; added 0 changes nothing.
|
|
131
|
+
|
|
132
|
+
Exit codes: 0 the file was written (or relocate found nothing to add); 2
|
|
133
|
+
an input that cannot be read or parsed, or an output that cannot be
|
|
134
|
+
written ({"status": "error", "error"} on stdout), or a usage error
|
|
135
|
+
(argparse, on stderr).
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
from __future__ import annotations
|
|
139
|
+
|
|
140
|
+
import argparse
|
|
141
|
+
import importlib.util
|
|
142
|
+
import json
|
|
143
|
+
import sys
|
|
144
|
+
from collections import Counter
|
|
145
|
+
from pathlib import Path
|
|
146
|
+
|
|
147
|
+
STATUSES = ("extracted", "read-by-eye", "hash-tracked", "missing", "parse-failed", "unread")
|
|
148
|
+
TO_READ = ("parse-failed", "unread")
|
|
149
|
+
RUNNER_READ = frozenset({"ok", "incomplete"})
|
|
150
|
+
AST_GREP_UNAVAILABLE = "all files (ast-grep unavailable)"
|
|
151
|
+
EXIT_ERROR = 2
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class SnapshotError(Exception):
|
|
155
|
+
"""An input or output the helper cannot use (exit 2)."""
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
_PROVENANCE = None
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _provenance():
|
|
162
|
+
"""skf-load-provenance.py, installed beside this script, loaded once."""
|
|
163
|
+
global _PROVENANCE
|
|
164
|
+
if _PROVENANCE is None:
|
|
165
|
+
path = Path(__file__).resolve().parent / "skf-load-provenance.py"
|
|
166
|
+
spec = importlib.util.spec_from_file_location("skf_load_provenance", path)
|
|
167
|
+
if spec is None or spec.loader is None:
|
|
168
|
+
raise SnapshotError(f"cannot load skf-load-provenance.py beside {Path(__file__).name}")
|
|
169
|
+
module = importlib.util.module_from_spec(spec)
|
|
170
|
+
try:
|
|
171
|
+
spec.loader.exec_module(module)
|
|
172
|
+
except (OSError, ImportError, SyntaxError) as exc:
|
|
173
|
+
raise SnapshotError(f"cannot load skf-load-provenance.py: {exc}") from exc
|
|
174
|
+
_PROVENANCE = module
|
|
175
|
+
return _PROVENANCE
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _posix(path: str) -> str:
|
|
179
|
+
"""A path as the scan list writes it: forward slashes, no leading `./`."""
|
|
180
|
+
rel = path.strip().replace("\\", "/")
|
|
181
|
+
while rel.startswith("./"):
|
|
182
|
+
rel = rel[2:]
|
|
183
|
+
return rel
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _read_json(path: Path, what: str) -> object:
|
|
187
|
+
try:
|
|
188
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
189
|
+
except OSError as exc:
|
|
190
|
+
raise SnapshotError(f"cannot read {what} {path}: {exc.strerror or exc}") from exc
|
|
191
|
+
except (UnicodeDecodeError, ValueError) as exc:
|
|
192
|
+
raise SnapshotError(f"{what} {path} is not JSON: {exc}") from exc
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _read_object(path: Path, what: str) -> dict:
|
|
196
|
+
data = _read_json(path, what)
|
|
197
|
+
if not isinstance(data, dict):
|
|
198
|
+
raise SnapshotError(f"{what} {path} must be a JSON object")
|
|
199
|
+
return data
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _write(path: Path, data: object) -> None:
|
|
203
|
+
try:
|
|
204
|
+
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
|
|
205
|
+
except OSError as exc:
|
|
206
|
+
raise SnapshotError(f"cannot write {path}: {exc.strerror or exc}") from exc
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _first(entry: dict, *keys: str) -> object:
|
|
210
|
+
for key in keys:
|
|
211
|
+
if entry.get(key) is not None:
|
|
212
|
+
return entry[key]
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
# --------------------------------------------------------------------------
|
|
217
|
+
# Building the snapshot
|
|
218
|
+
# --------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _runner_export(export: dict) -> dict:
|
|
222
|
+
return {
|
|
223
|
+
"name": export.get("export_name"),
|
|
224
|
+
"type": export.get("export_type"),
|
|
225
|
+
"signature": _first(export, "signature", "signature_line"),
|
|
226
|
+
"file": _posix(str(export.get("source_file") or "")),
|
|
227
|
+
"line": export.get("source_line"),
|
|
228
|
+
"confidence": export.get("confidence") or "T1",
|
|
229
|
+
"extraction_method": export.get("extraction_method") or "ast-grep",
|
|
230
|
+
"ast_node_type": export.get("ast_node_type"),
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _details_export(export: dict) -> dict | None:
|
|
235
|
+
name, file = _first(export, "name", "export_name"), _first(export, "file", "source_file")
|
|
236
|
+
if not isinstance(name, str) or not name.strip() or not isinstance(file, str) or not file.strip():
|
|
237
|
+
return None
|
|
238
|
+
return {
|
|
239
|
+
"name": name.strip(),
|
|
240
|
+
"type": _first(export, "type", "export_type"),
|
|
241
|
+
"signature": export.get("signature"),
|
|
242
|
+
"file": _posix(file),
|
|
243
|
+
"line": _first(export, "line", "source_line"),
|
|
244
|
+
"confidence": export.get("confidence") or "T1-low",
|
|
245
|
+
"extraction_method": export.get("extraction_method") or "source-read",
|
|
246
|
+
"ast_node_type": export.get("ast_node_type"),
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _details(path: Path) -> tuple[list[str], list[dict]]:
|
|
251
|
+
data = _read_object(path, "details file")
|
|
252
|
+
files, exports = data.get("files", []), data.get("exports", [])
|
|
253
|
+
if not isinstance(files, list) or not all(isinstance(f, str) for f in files):
|
|
254
|
+
raise SnapshotError(f"details file {path}: `files` must be a list of paths")
|
|
255
|
+
if not isinstance(exports, list) or not all(isinstance(e, dict) for e in exports):
|
|
256
|
+
raise SnapshotError(f"details file {path}: `exports` must be a list of objects")
|
|
257
|
+
return [_posix(f) for f in files], exports
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _entry_point_diff(runner: dict) -> dict:
|
|
261
|
+
diff = runner.get("entry_point_diff")
|
|
262
|
+
return diff if isinstance(diff, dict) else {}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _outside_scope(runner: dict) -> list[dict]:
|
|
266
|
+
grouped: dict[str, dict] = {}
|
|
267
|
+
for item in _entry_point_diff(runner).get("outside_scope") or []:
|
|
268
|
+
if not isinstance(item, dict) or not isinstance(item.get("file"), str) or not item.get("name"):
|
|
269
|
+
continue
|
|
270
|
+
group = grouped.setdefault(_posix(item["file"]), {"names": [], "entries": []})
|
|
271
|
+
if item["name"] not in group["names"]:
|
|
272
|
+
group["names"].append(item["name"])
|
|
273
|
+
entry = item.get("entry")
|
|
274
|
+
if isinstance(entry, str) and entry not in group["entries"]:
|
|
275
|
+
group["entries"].append(entry)
|
|
276
|
+
return [{"path": path, "names": sorted(g["names"]), "entries": sorted(g["entries"])}
|
|
277
|
+
for path, g in sorted(grouped.items())]
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def _extraction_gaps(runner: dict, exports: list[dict]) -> list[dict]:
|
|
281
|
+
"""The runner's extraction gaps no export of the snapshot holds yet."""
|
|
282
|
+
held = {(e["name"], e["file"]) for e in exports}
|
|
283
|
+
names = {e["name"] for e in exports}
|
|
284
|
+
gaps = []
|
|
285
|
+
for item in _entry_point_diff(runner).get("extraction_gaps") or []:
|
|
286
|
+
if not isinstance(item, dict) or not isinstance(item.get("name"), str) or not item["name"]:
|
|
287
|
+
continue
|
|
288
|
+
file = _posix(item["file"]) if isinstance(item.get("file"), str) else None
|
|
289
|
+
if (item["name"], file) in held or (file is None and item["name"] in names):
|
|
290
|
+
continue
|
|
291
|
+
gap = {"name": item["name"], "file": file, "line": item.get("line"), "entry": item.get("entry")}
|
|
292
|
+
if gap not in gaps:
|
|
293
|
+
gaps.append(gap)
|
|
294
|
+
return gaps
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _ordered(exports) -> list[dict]:
|
|
298
|
+
return sorted(exports, key=lambda e: (e["file"], e["line"] if isinstance(e["line"], int) else 0, e["name"]))
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _counts(exports: list[dict]) -> dict:
|
|
302
|
+
by_type = Counter(str(e["type"]) for e in exports if e.get("type") is not None)
|
|
303
|
+
t1 = sum(1 for e in exports if e.get("confidence") == "T1")
|
|
304
|
+
return {"exports": len(exports), "by_type": dict(sorted(by_type.items())), "t1": t1,
|
|
305
|
+
"t1_low": len(exports) - t1}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def build(source_root: Path, tier: str, date: str, provenance: dict | None, runner: dict | None,
|
|
309
|
+
details_files: list[str], details_exports: list[dict], root_text: str | None = None) -> dict:
|
|
310
|
+
"""The snapshot (see the module docstring)."""
|
|
311
|
+
warnings: list[str] = []
|
|
312
|
+
runner = runner or {}
|
|
313
|
+
runner_status = runner.get("status") if runner else None
|
|
314
|
+
ran = runner_status in RUNNER_READ
|
|
315
|
+
issues = {_posix(i["file"]): i.get("issue") for i in runner.get("file_issues") or []
|
|
316
|
+
if isinstance(i, dict) and isinstance(i.get("file"), str)}
|
|
317
|
+
unread = {_posix(f) for e in runner.get("errors") or [] if isinstance(e, dict)
|
|
318
|
+
for f in e.get("unread") or [] if isinstance(f, str)}
|
|
319
|
+
runner_exports = [_runner_export(e) for e in runner.get("exports") or []
|
|
320
|
+
if isinstance(e, dict) and e.get("export_name")] if ran else []
|
|
321
|
+
extra = [x for x in (_details_export(e) for e in details_exports) if x is not None]
|
|
322
|
+
if len(extra) < len(details_exports):
|
|
323
|
+
warnings.append(f"{len(details_exports) - len(extra)} details export(s) name no export or no file: "
|
|
324
|
+
"left out")
|
|
325
|
+
|
|
326
|
+
if provenance is not None:
|
|
327
|
+
scope = list(_provenance().bounded_scan_files(provenance))
|
|
328
|
+
# the files a code entry names; any other is a file_entries[] row
|
|
329
|
+
code_files = set(_provenance().bounded_scan_files({"entries": provenance.get("entries")}))
|
|
330
|
+
libraries = _provenance().source_library_by_file(provenance)
|
|
331
|
+
else:
|
|
332
|
+
named = [e["file"] for e in runner_exports + extra] + list(issues) + list(unread) + details_files
|
|
333
|
+
scope = sorted(set(f for f in named if f))
|
|
334
|
+
code_files = set(scope)
|
|
335
|
+
libraries = {}
|
|
336
|
+
in_scope = set(scope)
|
|
337
|
+
|
|
338
|
+
exports: dict[tuple[str, str], dict] = {}
|
|
339
|
+
hashed = Counter()
|
|
340
|
+
for record in runner_exports + extra:
|
|
341
|
+
if record["file"] not in in_scope:
|
|
342
|
+
warnings.append(f"{record['name']} in {record['file']}: the file is outside the files in scope, "
|
|
343
|
+
"left out")
|
|
344
|
+
continue
|
|
345
|
+
if record["file"] not in code_files:
|
|
346
|
+
hashed[record["file"]] += 1
|
|
347
|
+
continue
|
|
348
|
+
key = (record["name"], record["file"])
|
|
349
|
+
held = exports.get(key)
|
|
350
|
+
if held is None:
|
|
351
|
+
exports[key] = record
|
|
352
|
+
else:
|
|
353
|
+
for field, value in record.items():
|
|
354
|
+
if held.get(field) is None and value is not None:
|
|
355
|
+
held[field] = value
|
|
356
|
+
warnings += [f"{count} export(s) in {file}: only file_entries[] names the file, which step 3 compares "
|
|
357
|
+
"by hash, left out" for file, count in sorted(hashed.items())]
|
|
358
|
+
per_file = Counter(file for _, file in exports)
|
|
359
|
+
|
|
360
|
+
files = []
|
|
361
|
+
read_by_eye = set(details_files)
|
|
362
|
+
for rel in scope:
|
|
363
|
+
issue = issues.get(rel)
|
|
364
|
+
if not (source_root / rel).is_file():
|
|
365
|
+
runner_said = "missing"
|
|
366
|
+
elif rel not in code_files:
|
|
367
|
+
runner_said = "hash-tracked"
|
|
368
|
+
elif ran and issue is not None and issue != "missing":
|
|
369
|
+
runner_said = "parse-failed"
|
|
370
|
+
elif not ran or (runner_status == "incomplete" and (rel in unread or not per_file[rel])):
|
|
371
|
+
runner_said = "unread"
|
|
372
|
+
else:
|
|
373
|
+
runner_said = "extracted"
|
|
374
|
+
by_eye = rel in read_by_eye and runner_said not in ("missing", "hash-tracked")
|
|
375
|
+
status = "read-by-eye" if by_eye else runner_said
|
|
376
|
+
files.append({"file": rel, "status": status,
|
|
377
|
+
"issue": issue if runner_said in ("parse-failed", "hash-tracked") else None,
|
|
378
|
+
"fallback": ran and runner_said in TO_READ, "exports": per_file[rel]})
|
|
379
|
+
|
|
380
|
+
ordered = _ordered(exports.values())
|
|
381
|
+
for record in ordered:
|
|
382
|
+
library = libraries.get(record["file"])
|
|
383
|
+
if library is not None:
|
|
384
|
+
record["source_library"] = library
|
|
385
|
+
by_status = Counter(f["status"] for f in files)
|
|
386
|
+
recipes = [r for r in runner.get("recipes") or [] if isinstance(r, dict)]
|
|
387
|
+
cap_hits = [r.get("id") for r in recipes if r.get("truncated")]
|
|
388
|
+
if ran:
|
|
389
|
+
fallback_files = [f["file"] for f in files if f["fallback"]]
|
|
390
|
+
else:
|
|
391
|
+
fallback_files = [] if tier.strip().lower() == "quick" else [AST_GREP_UNAVAILABLE]
|
|
392
|
+
return {
|
|
393
|
+
"extraction_date": date,
|
|
394
|
+
"confidence_tier": tier,
|
|
395
|
+
"source_root": root_text if root_text is not None else str(source_root),
|
|
396
|
+
"bounded_scan": provenance is not None,
|
|
397
|
+
"bounded_scan_source": "provenance-map" if provenance is not None else "source-tree-fallback",
|
|
398
|
+
"runner_status": runner_status,
|
|
399
|
+
"runner_errors": [e for e in runner.get("errors") or [] if isinstance(e, dict)],
|
|
400
|
+
"files_scanned": len(scope),
|
|
401
|
+
"complete": not any(by_status[status] for status in TO_READ),
|
|
402
|
+
"files": files,
|
|
403
|
+
"files_by_status": {status: by_status[status] for status in STATUSES},
|
|
404
|
+
"ast_fallback_files": fallback_files,
|
|
405
|
+
"extraction_gaps": _extraction_gaps(runner, ordered) if ran else [],
|
|
406
|
+
"truncated": bool(runner.get("truncated")) or bool(cap_hits),
|
|
407
|
+
"cap_hits": cap_hits,
|
|
408
|
+
"outside_scope": _outside_scope(runner) if ran else [],
|
|
409
|
+
"counts": _counts(ordered),
|
|
410
|
+
"exports": ordered,
|
|
411
|
+
"relocations": [],
|
|
412
|
+
"warnings": warnings,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def relocate(snapshot: dict, diff: dict, runner: dict) -> list[dict]:
|
|
417
|
+
"""Add to `snapshot` each export `runner` found under a removed name of
|
|
418
|
+
`diff` in a file outside the snapshot's scope; the exports added."""
|
|
419
|
+
removed: dict[str, dict] = {}
|
|
420
|
+
for item in diff.get("removed") or []:
|
|
421
|
+
if isinstance(item, dict) and isinstance(item.get("name"), str):
|
|
422
|
+
removed.setdefault(item["name"], item)
|
|
423
|
+
exports = snapshot.get("exports")
|
|
424
|
+
if not isinstance(exports, list) or not all(isinstance(e, dict) for e in exports):
|
|
425
|
+
raise SnapshotError("the snapshot's `exports` must be a list of objects")
|
|
426
|
+
scope = {f.get("file") for f in snapshot.get("files") or [] if isinstance(f, dict)}
|
|
427
|
+
held = {(e.get("name"), e.get("file")) for e in exports}
|
|
428
|
+
added = []
|
|
429
|
+
if runner.get("status") in RUNNER_READ:
|
|
430
|
+
for export in runner.get("exports") or []:
|
|
431
|
+
if not isinstance(export, dict) or export.get("export_name") not in removed:
|
|
432
|
+
continue
|
|
433
|
+
record = _runner_export(export)
|
|
434
|
+
if record["file"] in scope or (record["name"], record["file"]) in held:
|
|
435
|
+
continue
|
|
436
|
+
library = removed[record["name"]].get("source_library")
|
|
437
|
+
if isinstance(library, str):
|
|
438
|
+
record["source_library"] = library
|
|
439
|
+
held.add((record["name"], record["file"]))
|
|
440
|
+
added.append(record)
|
|
441
|
+
if added:
|
|
442
|
+
snapshot["exports"] = _ordered(exports + added)
|
|
443
|
+
snapshot["counts"] = _counts(snapshot["exports"])
|
|
444
|
+
snapshot["relocations"] = list(snapshot.get("relocations") or []) + [
|
|
445
|
+
{"name": e["name"], "file": e["file"], "line": e["line"]} for e in added]
|
|
446
|
+
return added
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
# --------------------------------------------------------------------------
|
|
450
|
+
# CLI
|
|
451
|
+
# --------------------------------------------------------------------------
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def _cmd_scan_list(args: argparse.Namespace) -> dict:
|
|
455
|
+
provenance = _read_object(Path(args.provenance_map), "provenance map")
|
|
456
|
+
files = list(_provenance().bounded_scan_files(provenance))
|
|
457
|
+
_write(Path(args.output), files)
|
|
458
|
+
return {"status": "ok", "output": args.output, "files": len(files)}
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _cmd_build(args: argparse.Namespace) -> dict:
|
|
462
|
+
root = Path(args.source_root)
|
|
463
|
+
if not root.is_dir():
|
|
464
|
+
raise SnapshotError(f"source root not found: {args.source_root}")
|
|
465
|
+
provenance = _read_object(Path(args.provenance_map), "provenance map") if args.provenance_map else None
|
|
466
|
+
runner = _read_object(Path(args.extraction), "runner result") if args.extraction else None
|
|
467
|
+
details_files: list[str] = []
|
|
468
|
+
details_exports: list[dict] = []
|
|
469
|
+
for path in args.details or []:
|
|
470
|
+
files, exports = _details(Path(path))
|
|
471
|
+
details_files += files
|
|
472
|
+
details_exports += exports
|
|
473
|
+
snapshot = build(root, args.tier, args.date, provenance, runner, details_files, details_exports,
|
|
474
|
+
root_text=args.source_path or args.source_root)
|
|
475
|
+
_write(Path(args.output), snapshot)
|
|
476
|
+
return {"status": "ok", "output": args.output, "runner_status": snapshot["runner_status"],
|
|
477
|
+
"complete": snapshot["complete"],
|
|
478
|
+
"to_read": [{"file": f["file"], "status": f["status"], "issue": f["issue"]}
|
|
479
|
+
for f in snapshot["files"] if f["status"] in TO_READ],
|
|
480
|
+
"extraction_gaps": snapshot["extraction_gaps"], "ast_fallback_files": snapshot["ast_fallback_files"],
|
|
481
|
+
"files_by_status": snapshot["files_by_status"], "counts": snapshot["counts"]}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _cmd_relocate(args: argparse.Namespace) -> dict:
|
|
485
|
+
path = Path(args.snapshot)
|
|
486
|
+
snapshot = _read_object(path, "snapshot")
|
|
487
|
+
added = relocate(snapshot, _read_object(Path(args.diff), "structural diff"),
|
|
488
|
+
_read_object(Path(args.extraction), "runner result"))
|
|
489
|
+
if added:
|
|
490
|
+
_write(path, snapshot)
|
|
491
|
+
return {"status": "ok", "output": args.snapshot, "added": len(added),
|
|
492
|
+
"names": sorted({e["name"] for e in added})}
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
496
|
+
parser = argparse.ArgumentParser(
|
|
497
|
+
prog="skf-extraction-snapshot",
|
|
498
|
+
description=__doc__,
|
|
499
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
500
|
+
)
|
|
501
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
502
|
+
scan = sub.add_parser("scan-list", help="write the files the provenance map covers as a JSON list")
|
|
503
|
+
scan.add_argument("provenance_map", help="path to provenance-map.json")
|
|
504
|
+
scan.add_argument("-o", "--output", required=True, help="the JSON list to write")
|
|
505
|
+
scan.set_defaults(func=_cmd_scan_list)
|
|
506
|
+
snap = sub.add_parser("build", help="build extraction-snapshot.json")
|
|
507
|
+
snap.add_argument("--source-root", required=True, help="the source tree the runner read")
|
|
508
|
+
snap.add_argument("--source-path",
|
|
509
|
+
help="the source root the provenance map records, written as the snapshot's source_root "
|
|
510
|
+
"(default: --source-root)")
|
|
511
|
+
snap.add_argument("--tier", required=True, help="the forge tier the re-index ran at")
|
|
512
|
+
snap.add_argument("--date", required=True, help="the run's timestamp, recorded as extraction_date")
|
|
513
|
+
snap.add_argument("--provenance-map", help="the provenance map: its files are the files in scope")
|
|
514
|
+
snap.add_argument("--extraction", help="the JSON skf-extract-public-api.py --mode full wrote")
|
|
515
|
+
snap.add_argument("--details", action="append",
|
|
516
|
+
help="the exports read by eye: {\"files\": [...], \"exports\": [...]}; repeat per file")
|
|
517
|
+
snap.add_argument("-o", "--output", required=True, help="the snapshot to write")
|
|
518
|
+
snap.set_defaults(func=_cmd_build)
|
|
519
|
+
move = sub.add_parser("relocate", help="add the exports the runner found under removed names elsewhere")
|
|
520
|
+
move.add_argument("snapshot", help="extraction-snapshot.json, rewritten in place")
|
|
521
|
+
move.add_argument("--diff", required=True, help="the structural diff whose removed[] names to look for")
|
|
522
|
+
move.add_argument("--extraction", required=True, help="the runner's JSON over the candidate files")
|
|
523
|
+
move.set_defaults(func=_cmd_relocate)
|
|
524
|
+
return parser
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def main(argv: list[str] | None = None) -> int:
|
|
528
|
+
args = _build_parser().parse_args(argv)
|
|
529
|
+
try:
|
|
530
|
+
result = args.func(args)
|
|
531
|
+
except SnapshotError as exc:
|
|
532
|
+
print(json.dumps({"status": "error", "error": str(exc)}))
|
|
533
|
+
return EXIT_ERROR
|
|
534
|
+
print(json.dumps(result))
|
|
535
|
+
return 0
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
if __name__ == "__main__":
|
|
539
|
+
sys.exit(main())
|