bmad-module-skill-forge 2.1.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +51 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +14 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +117 -9
- package/src/shared/scripts/skf-detect-docs.py +892 -4
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +215 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +17 -6
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +55 -18
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -57
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -89
- package/src/skf-setup/references/detect-and-tier.md +58 -66
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -645
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
# /// script
|
|
2
|
-
# requires-python = ">=3.
|
|
2
|
+
# requires-python = ">=3.11"
|
|
3
3
|
# dependencies = ["pyyaml"]
|
|
4
4
|
# ///
|
|
5
5
|
"""SKF Detect Tools — Parallel tool detection + tier calculation for skf-setup.
|
|
6
6
|
|
|
7
7
|
Replaces the prose-driven tool-detection sequence in `src/skf-setup/references/
|
|
8
8
|
detect-and-tier.md` §3-§8b with one Python invocation. Probes ast-grep,
|
|
9
|
-
gh, qmd, and
|
|
10
|
-
|
|
11
|
-
sanity check) and --require-tier (with tool-prerequisite
|
|
12
|
-
the tier name), and emits one JSON document on stdout.
|
|
9
|
+
gh, qmd, ccc, git and uv concurrently, holds each to its minimum version,
|
|
10
|
+
applies the 4-rule tier decision table (calculate_tier() below), evaluates
|
|
11
|
+
--tier-override (with sanity check) and --require-tier (with tool-prerequisite
|
|
12
|
+
check independent of the tier name), and emits one JSON document on stdout.
|
|
13
|
+
|
|
14
|
+
Minimum versions come from tool-requirements.yaml in the shared/ folder
|
|
15
|
+
above this script's scripts/ folder, in the source tree and in an installed
|
|
16
|
+
project alike. Each probed tool gets `minimum`, `meets_minimum` (null when
|
|
17
|
+
its version cannot be read or it has no minimum) and `below_minimum`. A tier
|
|
18
|
+
tool below its minimum is reported like a stopped qmd, `available: false`,
|
|
19
|
+
so the tier and --require-tier leave it out with no change to the rules
|
|
20
|
+
below; git and uv are no tier tools, so they only warn. A version the
|
|
21
|
+
script cannot read, a null minimum or a list it cannot read never lowers a
|
|
22
|
+
tier. `tools_below_minimum` lists each tool below its minimum with what the
|
|
23
|
+
setup report and envelope need to name it, since skf-emit-result-envelope.py
|
|
24
|
+
reads no YAML.
|
|
13
25
|
|
|
14
26
|
Schema documented in DETECT_OUTPUT_SCHEMA at the bottom of this docstring.
|
|
15
27
|
The output is consumed by step 1 prose, step 2 (forge-tier.yaml writer),
|
|
16
|
-
and step 4
|
|
28
|
+
and step 4, where skf-emit-result-envelope.py reads the copy step 1 keeps
|
|
29
|
+
in the run folder (detect-tools.json) for the status report and envelope.
|
|
17
30
|
|
|
18
31
|
Tier rules (first match wins):
|
|
19
32
|
Deep = ast-grep + gh-cli + qmd (all healthy)
|
|
@@ -25,6 +38,8 @@ CCC verification is two-step (matches step 1 §7):
|
|
|
25
38
|
Step A: `ccc --help` exits 0 AND output contains "CocoIndex Code" marker.
|
|
26
39
|
Rejects code2prompt-aliased-as-ccc and similar PATH shadowing.
|
|
27
40
|
Step B: `ccc doctor` succeeds (daemon healthy).
|
|
41
|
+
ccc has no version flag: its version is the cocoindex-code line of
|
|
42
|
+
`uv tool list` (`cocoindex-code v0.2.41`), null when uv did not install it.
|
|
28
43
|
|
|
29
44
|
QMD verification is two-step (matches step 1 §5 post-PR-#248):
|
|
30
45
|
Step A: `qmd --version` exits 0 (binary identity, falls back to --help).
|
|
@@ -47,23 +62,32 @@ runtime prerequisite):
|
|
|
47
62
|
uv run skf-detect-tools.py --require-tier Forge+
|
|
48
63
|
uv run skf-detect-tools.py --snyk-env-var SNYK_TOKEN
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
for invocation consistency with sibling scripts that DO require pyyaml.
|
|
65
|
+
`uv run` installs pyyaml, which reads the prior state and the minimums;
|
|
66
|
+
under a bare `python3` without it, both read as absent.
|
|
53
67
|
|
|
54
68
|
DETECT_OUTPUT_SCHEMA (v1):
|
|
55
69
|
{
|
|
56
70
|
"status": "ok",
|
|
57
71
|
"version": "v1",
|
|
58
72
|
"tools": {
|
|
73
|
+
# Every probed tool also has "minimum": str|null, "meets_minimum":
|
|
74
|
+
# bool|null and "below_minimum": bool; "version" is the probe's first
|
|
75
|
+
# output line (ccc's is the bare version).
|
|
59
76
|
"ast_grep": {"available": bool, "version": str|null},
|
|
60
77
|
"gh_cli": {"available": bool, "version": str|null},
|
|
61
78
|
"qmd": {"available": bool, "status": "absent"|"daemon_stopped"|"healthy",
|
|
62
79
|
"version": str|null},
|
|
63
80
|
"ccc": {"available": bool, "daemon": "healthy"|"stopped"|"error"|null,
|
|
64
81
|
"version": str|null},
|
|
82
|
+
"git": {"available": bool, "version": str|null},
|
|
83
|
+
"uv": {"available": bool, "version": str|null},
|
|
65
84
|
"security_scan": {"available": bool}
|
|
66
85
|
},
|
|
86
|
+
"tools_below_minimum": [
|
|
87
|
+
# One per tool below its minimum, in the order of tool-requirements.yaml.
|
|
88
|
+
{"tool": str, "name": str, "version": str, "minimum": str,
|
|
89
|
+
"upgrade": str|null, "tier": "Forge"|"Forge+"|"Deep"|null}
|
|
90
|
+
],
|
|
67
91
|
"tier": {
|
|
68
92
|
"calculated": "Quick"|"Forge"|"Forge+"|"Deep",
|
|
69
93
|
"detected": "Quick"|"Forge"|"Forge+"|"Deep",
|
|
@@ -89,9 +113,11 @@ DETECT_OUTPUT_SCHEMA (v1):
|
|
|
89
113
|
"previous_ccc_indexed_path": str|null,
|
|
90
114
|
"previous_ccc_last_indexed": str|null,
|
|
91
115
|
"previous_ccc_staleness_threshold_hours": int|null,
|
|
116
|
+
"previous_ccc_file_count": int|null,
|
|
92
117
|
# Deterministic CCC-index freshness verdict — computed against
|
|
93
118
|
# --project-root and datetime.now(UTC) captured at detect time, so the
|
|
94
|
-
# ccc-index.md step
|
|
119
|
+
# ccc-index.md step forwards a boolean (as --index-fresh) to
|
|
120
|
+
# skf-merge-ccc-exclusions.py instead of doing timestamp math.
|
|
95
121
|
"ccc_index_fresh": bool
|
|
96
122
|
},
|
|
97
123
|
"deltas": {
|
|
@@ -107,12 +133,14 @@ from __future__ import annotations
|
|
|
107
133
|
import argparse
|
|
108
134
|
import json
|
|
109
135
|
import os
|
|
136
|
+
import re
|
|
110
137
|
import shutil
|
|
111
138
|
import subprocess
|
|
112
139
|
import sys
|
|
113
140
|
import tempfile
|
|
114
141
|
from concurrent.futures import ThreadPoolExecutor
|
|
115
142
|
from datetime import datetime, timezone
|
|
143
|
+
from pathlib import Path
|
|
116
144
|
|
|
117
145
|
|
|
118
146
|
VALID_TIERS = ("Quick", "Forge", "Forge+", "Deep")
|
|
@@ -129,6 +157,13 @@ PROBE_TIMEOUT_SEC = 8 # per-tool subprocess.run timeout
|
|
|
129
157
|
# own timeout. None when unavailable.
|
|
130
158
|
_TIMEOUT_BIN = shutil.which("timeout") if os.name == "posix" else None
|
|
131
159
|
CCC_IDENTITY_MARKER = "cocoindex code" # case-insensitive substring
|
|
160
|
+
# The one list of tool versions: shared/ holds it beside scripts/.
|
|
161
|
+
REQUIREMENTS_FILE = Path(__file__).resolve().parent.parent / "tool-requirements.yaml"
|
|
162
|
+
# The first x.y or x.y.z of a version line: `gh version 2.101.0 (2026-09-15)`.
|
|
163
|
+
VERSION_RE = re.compile(r"(\d+)\.(\d+)(?:\.(\d+))?")
|
|
164
|
+
MINIMUM_RE = re.compile(r"\d+(?:\.\d+)*")
|
|
165
|
+
# `uv tool list` names each package uv installed, with its version.
|
|
166
|
+
CCC_PACKAGE_RE = re.compile(r"^cocoindex-code v(\S+)", re.MULTILINE)
|
|
132
167
|
|
|
133
168
|
|
|
134
169
|
def _die(code: int, message: str) -> None:
|
|
@@ -150,7 +185,10 @@ def _resolve_outside_cwd(command: str) -> str | None:
|
|
|
150
185
|
into CWD would execute a repo-planted shim (e.g. ast-grep.cmd). Such a
|
|
151
186
|
resolution is treated as not-found. Explicit paths supplied by callers
|
|
152
187
|
(containing a separator) are honored as-is. Keep identical to the
|
|
153
|
-
sibling
|
|
188
|
+
sibling guards in skf-qmd-classify-collections.py,
|
|
189
|
+
skf-merge-ccc-exclusions.py, skf-ccc-git-hygiene.py,
|
|
190
|
+
skf-source-tree.py, skf-tessl-review.py and
|
|
191
|
+
skf-verify-provenance-completeness.py.
|
|
154
192
|
"""
|
|
155
193
|
resolved = shutil.which(command)
|
|
156
194
|
if resolved is None:
|
|
@@ -234,18 +272,30 @@ def _first_line(text: str) -> str | None:
|
|
|
234
272
|
return None
|
|
235
273
|
|
|
236
274
|
|
|
237
|
-
def
|
|
238
|
-
|
|
275
|
+
def _probe_version(command: str) -> dict:
|
|
276
|
+
"""One `<command> --version` call: available, and the first output line."""
|
|
277
|
+
rc, stdout, _ = _run([command, "--version"])
|
|
239
278
|
if rc != 0:
|
|
240
279
|
return {"available": False, "version": None}
|
|
241
280
|
return {"available": True, "version": _first_line(stdout)}
|
|
242
281
|
|
|
243
282
|
|
|
283
|
+
def probe_ast_grep() -> dict:
|
|
284
|
+
return _probe_version("ast-grep")
|
|
285
|
+
|
|
286
|
+
|
|
244
287
|
def probe_gh_cli() -> dict:
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
288
|
+
return _probe_version("gh")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def probe_git() -> dict:
|
|
292
|
+
"""Not a tier tool: held to its minimum, which only warns."""
|
|
293
|
+
return _probe_version("git")
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def probe_uv() -> dict:
|
|
297
|
+
"""Not a tier tool: held to its minimum, which only warns."""
|
|
298
|
+
return _probe_version("uv")
|
|
249
299
|
|
|
250
300
|
|
|
251
301
|
def probe_qmd() -> dict:
|
|
@@ -288,11 +338,94 @@ def probe_ccc() -> dict:
|
|
|
288
338
|
return {"available": True, "daemon": "error", "version": version}
|
|
289
339
|
|
|
290
340
|
|
|
341
|
+
def probe_ccc_version() -> str | None:
|
|
342
|
+
"""ccc's version from `uv tool list`, or None when uv did not install it."""
|
|
343
|
+
rc, stdout, _ = _run(["uv", "tool", "list"])
|
|
344
|
+
match = CCC_PACKAGE_RE.search(stdout) if rc == 0 else None
|
|
345
|
+
return found_version(match.group(1)) if match else None
|
|
346
|
+
|
|
347
|
+
|
|
291
348
|
def probe_security_scan(env_var: str) -> dict:
|
|
292
349
|
"""Informational only — does NOT affect tier."""
|
|
293
350
|
return {"available": bool(os.environ.get(env_var, "").strip())}
|
|
294
351
|
|
|
295
352
|
|
|
353
|
+
def load_requirements(path=None) -> dict:
|
|
354
|
+
"""The tools of tool-requirements.yaml by key, or {} when it cannot be read.
|
|
355
|
+
|
|
356
|
+
A list that is missing or not YAML gives no tool a minimum, so it holds
|
|
357
|
+
none back.
|
|
358
|
+
"""
|
|
359
|
+
try:
|
|
360
|
+
import yaml # local import, as in read_prior_state
|
|
361
|
+
data = yaml.safe_load(Path(path or REQUIREMENTS_FILE).read_text(encoding="utf-8"))
|
|
362
|
+
except Exception:
|
|
363
|
+
return {}
|
|
364
|
+
tools = data.get("tools") if isinstance(data, dict) else None
|
|
365
|
+
if not isinstance(tools, dict):
|
|
366
|
+
return {}
|
|
367
|
+
return {key: entry for key, entry in tools.items() if isinstance(entry, dict)}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def found_version(text) -> str | None:
|
|
371
|
+
"""The first x.y or x.y.z of a version line, as written there, or None."""
|
|
372
|
+
match = VERSION_RE.search(text) if isinstance(text, str) else None
|
|
373
|
+
return match.group(0) if match else None
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def meets_minimum(version, minimum) -> bool | None:
|
|
377
|
+
"""Whether a probed version is at least the minimum, missing parts read as 0.
|
|
378
|
+
|
|
379
|
+
None when either cannot be read: a version SKF cannot parse, or a null
|
|
380
|
+
minimum, never holds a tool back.
|
|
381
|
+
"""
|
|
382
|
+
found = found_version(version)
|
|
383
|
+
if found is None or not isinstance(minimum, str) or not MINIMUM_RE.fullmatch(minimum):
|
|
384
|
+
return None
|
|
385
|
+
have = [int(part) for part in found.split(".")]
|
|
386
|
+
need = [int(part) for part in minimum.split(".")]
|
|
387
|
+
width = max(len(have), len(need))
|
|
388
|
+
return have + [0] * (width - len(have)) >= need + [0] * (width - len(need))
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def apply_minimums(tools: dict, requirements: dict) -> list[dict]:
|
|
392
|
+
"""Hold each probed tool to its minimum; return the tools below theirs.
|
|
393
|
+
|
|
394
|
+
Adds `minimum`, `meets_minimum` and `below_minimum` to every probed tool
|
|
395
|
+
(security_scan is none). A tier tool below its minimum becomes
|
|
396
|
+
`available: false`, so calculate_tier() and tier_prerequisites_met()
|
|
397
|
+
leave it out; any other kind only warns. Each tool below its minimum
|
|
398
|
+
gets one entry, in the list's order, naming it, its version and minimum,
|
|
399
|
+
how to upgrade it and, for a tier tool, the first tier it counts toward.
|
|
400
|
+
"""
|
|
401
|
+
below = []
|
|
402
|
+
order = [*requirements, *(key for key in tools if key not in requirements)]
|
|
403
|
+
for key in order:
|
|
404
|
+
probe = tools.get(key)
|
|
405
|
+
if key == "security_scan" or not isinstance(probe, dict):
|
|
406
|
+
continue
|
|
407
|
+
entry = requirements.get(key, {})
|
|
408
|
+
minimum = entry.get("minimum") if isinstance(entry.get("minimum"), str) else None
|
|
409
|
+
meets = meets_minimum(probe.get("version"), minimum)
|
|
410
|
+
probe.update({"minimum": minimum, "meets_minimum": meets, "below_minimum": meets is False})
|
|
411
|
+
if meets is not False:
|
|
412
|
+
continue
|
|
413
|
+
tier_tool = entry.get("kind") == "tier"
|
|
414
|
+
if tier_tool:
|
|
415
|
+
probe["available"] = False
|
|
416
|
+
tiers = entry.get("tiers") if isinstance(entry.get("tiers"), list) else []
|
|
417
|
+
upgrade = entry.get("upgrade")
|
|
418
|
+
below.append({
|
|
419
|
+
"tool": key,
|
|
420
|
+
"name": str(entry.get("name") or key),
|
|
421
|
+
"version": found_version(probe.get("version")),
|
|
422
|
+
"minimum": minimum,
|
|
423
|
+
"upgrade": " ".join(upgrade.split()) if isinstance(upgrade, str) and upgrade.strip() else None,
|
|
424
|
+
"tier": str(tiers[0]) if tier_tool and tiers else None,
|
|
425
|
+
})
|
|
426
|
+
return below
|
|
427
|
+
|
|
428
|
+
|
|
296
429
|
def calculate_tier(tools: dict) -> str:
|
|
297
430
|
ag = tools["ast_grep"]["available"]
|
|
298
431
|
gh = tools["gh_cli"]["available"]
|
|
@@ -309,7 +442,7 @@ def calculate_tier(tools: dict) -> str:
|
|
|
309
442
|
|
|
310
443
|
|
|
311
444
|
def suggest_valid_tier(bad_value: str) -> str | None:
|
|
312
|
-
"""For an invalid --tier-override value, return the closest valid tier name.
|
|
445
|
+
"""For an invalid --tier-override or --require-tier value, return the closest valid tier name.
|
|
313
446
|
|
|
314
447
|
Two-stage match:
|
|
315
448
|
1. Case-insensitive exact match — handles `deep` / `DEEP` / `forge+` /
|
|
@@ -335,6 +468,27 @@ def suggest_valid_tier(bad_value: str) -> str | None:
|
|
|
335
468
|
return matches[0] if matches else None
|
|
336
469
|
|
|
337
470
|
|
|
471
|
+
def require_tier_error(value) -> str | None:
|
|
472
|
+
"""The user error for a --require-tier value that is not a tier, or None.
|
|
473
|
+
|
|
474
|
+
The check is exact (case-sensitive), so a typo fails closed instead of
|
|
475
|
+
switching the tier gate off. The message names the valid tiers, adds
|
|
476
|
+
suggest_valid_tier's did-you-mean when one clears its cutoff, and holds
|
|
477
|
+
no quote, backslash or control character, even when the value does:
|
|
478
|
+
skf-setup carries it into its blocked envelope's reason, inside a
|
|
479
|
+
single-quoted shell payload.
|
|
480
|
+
"""
|
|
481
|
+
if value in VALID_TIERS:
|
|
482
|
+
return None
|
|
483
|
+
valid = ", ".join(VALID_TIERS)
|
|
484
|
+
safe = {"'": "`", '"': "`", "\\": "/"}
|
|
485
|
+
shown = "".join("?" if ord(ch) < 0x20 or ord(ch) == 0x7F else safe.get(ch, ch) for ch in str(value))
|
|
486
|
+
message = (f"--require-tier must be one of {valid} (case-sensitive), got "
|
|
487
|
+
+ (shown if shown.strip() else "an empty value"))
|
|
488
|
+
suggestion = suggest_valid_tier(value)
|
|
489
|
+
return f"{message}; did you mean {suggestion}?" if suggestion else message
|
|
490
|
+
|
|
491
|
+
|
|
338
492
|
def tier_prerequisites_met(tier: str, tools: dict) -> tuple[bool, list[str]]:
|
|
339
493
|
"""Return (satisfied, missing_tools) for tier-prerequisite checks.
|
|
340
494
|
|
|
@@ -369,6 +523,7 @@ def read_prior_state(prior_state_path) -> dict:
|
|
|
369
523
|
"previous_ccc_indexed_path": None,
|
|
370
524
|
"previous_ccc_last_indexed": None,
|
|
371
525
|
"previous_ccc_staleness_threshold_hours": None,
|
|
526
|
+
"previous_ccc_file_count": None,
|
|
372
527
|
}
|
|
373
528
|
if not prior_state_path:
|
|
374
529
|
return empty
|
|
@@ -393,15 +548,19 @@ def read_prior_state(prior_state_path) -> dict:
|
|
|
393
548
|
"previous_ccc_indexed_path": ccc_index.get("indexed_path"),
|
|
394
549
|
"previous_ccc_last_indexed": ccc_index.get("last_indexed"),
|
|
395
550
|
"previous_ccc_staleness_threshold_hours": ccc_index.get("staleness_threshold_hours"),
|
|
551
|
+
# Surfaced so ccc-index.md's fresh-index branch can carry the count
|
|
552
|
+
# forward instead of leaving `{ccc_file_count}` unbound (or nulling a
|
|
553
|
+
# value the index still has) when nothing re-indexes this run.
|
|
554
|
+
"previous_ccc_file_count": ccc_index.get("file_count"),
|
|
396
555
|
}
|
|
397
556
|
|
|
398
557
|
|
|
399
558
|
def _parse_iso_timestamp(value) -> datetime | None:
|
|
400
559
|
"""Parse an ISO 8601 timestamp into a timezone-aware datetime, or None.
|
|
401
560
|
|
|
402
|
-
Normalizes a trailing 'Z' (UTC designator) to '+00:00' because
|
|
403
|
-
`datetime.fromisoformat`
|
|
404
|
-
|
|
561
|
+
Normalizes a trailing 'Z' or 'z' (UTC designator) to '+00:00' because
|
|
562
|
+
`datetime.fromisoformat` accepts 'Z' from Python 3.11 on (this script's
|
|
563
|
+
floor) but still rejects 'z'. A naive result (no tzinfo) is assumed to be
|
|
405
564
|
UTC so it can be compared against a tz-aware `now` without raising. Returns
|
|
406
565
|
None on any parse failure (non-string, empty, malformed).
|
|
407
566
|
"""
|
|
@@ -434,7 +593,7 @@ def compute_ccc_index_fresh(prior: dict, project_root, now: datetime) -> bool:
|
|
|
434
593
|
- last_indexed parses AND (now - last_indexed) <= staleness threshold
|
|
435
594
|
|
|
436
595
|
The staleness threshold defaults to 24 hours when the prior field is null
|
|
437
|
-
(
|
|
596
|
+
(the staleness default in knowledge/ccc-bridge.md). Any null/unparseable required field
|
|
438
597
|
(indexed_path, status, last_indexed), path mismatch, non-fresh status,
|
|
439
598
|
unparseable threshold, or over-threshold delta yields False.
|
|
440
599
|
"""
|
|
@@ -464,16 +623,29 @@ def compute_ccc_index_fresh(prior: dict, project_root, now: datetime) -> bool:
|
|
|
464
623
|
|
|
465
624
|
|
|
466
625
|
def detect(args: argparse.Namespace) -> dict:
|
|
626
|
+
# A --require-tier that names no tier is a user error: stop before
|
|
627
|
+
# probing anything, so the caller's halt comes at once.
|
|
628
|
+
if args.require_tier is not None:
|
|
629
|
+
error = require_tier_error(args.require_tier)
|
|
630
|
+
if error:
|
|
631
|
+
_die(1, error)
|
|
632
|
+
|
|
467
633
|
tools: dict = {}
|
|
468
|
-
with ThreadPoolExecutor(max_workers=
|
|
634
|
+
with ThreadPoolExecutor(max_workers=7) as ex:
|
|
469
635
|
futures = {
|
|
470
636
|
"ast_grep": ex.submit(probe_ast_grep),
|
|
471
637
|
"gh_cli": ex.submit(probe_gh_cli),
|
|
472
638
|
"qmd": ex.submit(probe_qmd),
|
|
473
639
|
"ccc": ex.submit(probe_ccc),
|
|
640
|
+
"git": ex.submit(probe_git),
|
|
641
|
+
"uv": ex.submit(probe_uv),
|
|
474
642
|
}
|
|
643
|
+
ccc_version = ex.submit(probe_ccc_version)
|
|
475
644
|
for key, fut in futures.items():
|
|
476
645
|
tools[key] = fut.result()
|
|
646
|
+
if tools["ccc"]["available"]:
|
|
647
|
+
tools["ccc"]["version"] = ccc_version.result()
|
|
648
|
+
tools_below_minimum = apply_minimums(tools, load_requirements())
|
|
477
649
|
tools["security_scan"] = probe_security_scan(args.snyk_env_var)
|
|
478
650
|
|
|
479
651
|
detected = calculate_tier(tools)
|
|
@@ -508,8 +680,6 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
508
680
|
require_satisfied: bool | None
|
|
509
681
|
require_missing: list[str] = []
|
|
510
682
|
if args.require_tier is not None:
|
|
511
|
-
if args.require_tier not in VALID_TIERS:
|
|
512
|
-
_die(1, f"--require-tier must be one of {VALID_TIERS}, got {args.require_tier!r}")
|
|
513
683
|
require_satisfied, require_missing = tier_prerequisites_met(args.require_tier, tools)
|
|
514
684
|
else:
|
|
515
685
|
require_satisfied = None
|
|
@@ -522,6 +692,7 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
522
692
|
|
|
523
693
|
return {
|
|
524
694
|
"tools": tools,
|
|
695
|
+
"tools_below_minimum": tools_below_minimum,
|
|
525
696
|
"tier": {
|
|
526
697
|
"calculated": calculated,
|
|
527
698
|
"detected": detected,
|
|
@@ -589,9 +760,10 @@ def main() -> None:
|
|
|
589
760
|
"--require-tier",
|
|
590
761
|
default=None,
|
|
591
762
|
help="Require the calculated tier to satisfy this requirement (uses tool-prerequisite"
|
|
592
|
-
" check, not tier-name comparison
|
|
763
|
+
" check, not tier-name comparison: Deep does not subsume Forge+ because Deep"
|
|
593
764
|
" does not require ccc). Output reports satisfied/missing-tools; caller decides"
|
|
594
|
-
" whether to halt."
|
|
765
|
+
" whether to halt. A value that is not exactly Quick, Forge, Forge+ or Deep exits 1"
|
|
766
|
+
" before any probe runs, naming the valid tiers.",
|
|
595
767
|
)
|
|
596
768
|
parser.add_argument(
|
|
597
769
|
"--snyk-env-var",
|
|
@@ -623,5 +795,21 @@ def main() -> None:
|
|
|
623
795
|
_ok(payload)
|
|
624
796
|
|
|
625
797
|
|
|
798
|
+
def _force_utf8(*streams) -> None:
|
|
799
|
+
"""Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
|
|
800
|
+
|
|
801
|
+
A Windows console pipes them as cp1252, which cannot print every character
|
|
802
|
+
of the --help text, so --help would stop with UnicodeEncodeError.
|
|
803
|
+
"""
|
|
804
|
+
for stream in streams:
|
|
805
|
+
if hasattr(stream, "reconfigure"):
|
|
806
|
+
errors = getattr(stream, "errors", None)
|
|
807
|
+
if errors is None:
|
|
808
|
+
stream.reconfigure(encoding="utf-8")
|
|
809
|
+
else:
|
|
810
|
+
stream.reconfigure(encoding="utf-8", errors=errors)
|
|
811
|
+
|
|
812
|
+
|
|
626
813
|
if __name__ == "__main__":
|
|
814
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
627
815
|
main()
|