bmad-module-skill-forge 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/README.md +49 -49
- package/docs/404.md +10 -8
- package/docs/_data/pinned.yaml +22 -13
- package/docs/_internal/STABILITY.md +58 -35
- package/docs/agents.md +39 -53
- package/docs/architecture.md +65 -51
- package/docs/bmad-synergy.md +53 -51
- package/docs/campaign.md +127 -75
- package/docs/concepts.md +34 -28
- package/docs/examples.md +70 -68
- package/docs/forge-auto.md +34 -31
- package/docs/getting-started.md +118 -57
- package/docs/how-it-works.md +25 -21
- package/docs/index.md +15 -15
- package/docs/skill-model.md +70 -59
- package/docs/troubleshooting.md +135 -18
- package/docs/verifying-a-skill.md +83 -57
- package/docs/why-skf.md +24 -22
- package/docs/workflows.md +274 -110
- package/package.json +18 -10
- package/src/forger/preferences.yaml +8 -1
- package/src/knowledge/agentskills-spec.md +2 -0
- package/src/knowledge/ccc-bridge.md +40 -24
- package/src/knowledge/confidence-tiers.md +9 -5
- package/src/knowledge/overview.md +3 -3
- package/src/knowledge/progressive-capability.md +8 -8
- package/src/knowledge/provenance-tracking.md +9 -5
- package/src/knowledge/qmd-registry.md +2 -2
- package/src/knowledge/skf-knowledge-index.csv +2 -2
- package/src/knowledge/skill-lifecycle.md +18 -11
- package/src/knowledge/split-body-strategy.md +0 -3
- package/src/knowledge/tool-resolution.md +4 -2
- package/src/knowledge/version-paths.md +101 -15
- package/src/module-help.csv +3 -3
- package/src/module.yaml +10 -0
- package/src/shared/data/ast-grep-recipes.yaml +608 -0
- package/src/shared/data/ide-context-files.json +30 -0
- package/src/shared/health-check.md +32 -12
- package/src/shared/references/description-guard-protocol.md +12 -9
- package/src/shared/references/feasibility-report-schema.md +19 -13
- package/src/shared/references/headless-gate-convention.md +55 -3
- package/src/shared/references/output-contract-schema.md +19 -8
- package/src/shared/references/pipeline-contracts.md +94 -29
- package/src/shared/references/tessl-review.md +77 -0
- package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
- package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
- package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
- package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
- package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
- package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
- package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
- package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
- package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
- package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
- package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
- package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
- package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
- package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
- package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
- package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
- package/src/shared/scripts/skf-atomic-write.py +30 -14
- package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
- package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
- package/src/shared/scripts/skf-check-unit-records.py +184 -0
- package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
- package/src/shared/scripts/skf-classify-changed-files.py +694 -0
- package/src/shared/scripts/skf-comention-pairs.py +1333 -123
- package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
- package/src/shared/scripts/skf-count-imports.py +1867 -0
- package/src/shared/scripts/skf-count-tokens.py +1 -1
- package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
- package/src/shared/scripts/skf-description-guard.py +99 -11
- package/src/shared/scripts/skf-detect-docs.py +694 -2
- package/src/shared/scripts/skf-detect-language.py +486 -107
- package/src/shared/scripts/skf-detect-registry.py +887 -0
- package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
- package/src/shared/scripts/skf-detect-tools.py +209 -27
- package/src/shared/scripts/skf-detect-workspaces.py +506 -30
- package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
- package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
- package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
- package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
- package/src/shared/scripts/skf-extract-public-api.py +4084 -85
- package/src/shared/scripts/skf-extraction-inventory.py +746 -0
- package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
- package/src/shared/scripts/skf-fetch-temporal.py +674 -0
- package/src/shared/scripts/skf-find-cycles.py +97 -29
- package/src/shared/scripts/skf-find-test-report.py +476 -0
- package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
- package/src/shared/scripts/skf-github-fetch.py +365 -0
- package/src/shared/scripts/skf-github-probe.py +661 -0
- package/src/shared/scripts/skf-hash-content.py +166 -12
- package/src/shared/scripts/skf-language-corpora.py +1 -1
- package/src/shared/scripts/skf-load-provenance.py +159 -27
- package/src/shared/scripts/skf-manifest-ops.py +1 -1
- package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
- package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
- package/src/shared/scripts/skf-names-present.py +221 -0
- package/src/shared/scripts/skf-pair-intersect.py +119 -28
- package/src/shared/scripts/skf-parse-gaps.py +1384 -0
- package/src/shared/scripts/skf-preapply.py +1 -1
- package/src/shared/scripts/skf-preflight.py +161 -27
- package/src/shared/scripts/skf-promote-staged.py +430 -0
- package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
- package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
- package/src/shared/scripts/skf-quick-batch.py +774 -0
- package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
- package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
- package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
- package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
- package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
- package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
- package/src/shared/scripts/skf-resolve-package.py +523 -106
- package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
- package/src/shared/scripts/skf-run-lock.py +577 -0
- package/src/shared/scripts/skf-scan-manifests.py +998 -57
- package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
- package/src/shared/scripts/skf-severity-classify.py +712 -108
- package/src/shared/scripts/skf-shape-detect.py +298 -204
- package/src/shared/scripts/skf-shard-body.py +9 -8
- package/src/shared/scripts/skf-skill-inventory.py +2412 -89
- package/src/shared/scripts/skf-skills-module.py +472 -0
- package/src/shared/scripts/skf-source-tree.py +1796 -0
- package/src/shared/scripts/skf-structural-diff.py +670 -119
- package/src/shared/scripts/skf-tessl-review.py +706 -0
- package/src/shared/scripts/skf-update-active-symlink.py +5 -5
- package/src/shared/scripts/skf-update-run-state.py +624 -0
- package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
- package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
- package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
- package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
- package/src/shared/scripts/skf-validate-output.py +712 -64
- package/src/shared/scripts/skf-validate-pins.py +1 -1
- package/src/shared/scripts/skf-verify-no-trace.py +116 -10
- package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
- package/src/shared/scripts/skf-write-skill-brief.py +367 -19
- package/src/shared/tool-requirements.yaml +201 -0
- package/src/skf-analyze-source/SKILL.md +41 -36
- package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
- package/src/skf-analyze-source/customize.toml +36 -24
- package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
- package/src/skf-analyze-source/references/continue.md +2 -13
- package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
- package/src/skf-analyze-source/references/generate-briefs.md +121 -55
- package/src/skf-analyze-source/references/headless-contract.md +42 -15
- package/src/skf-analyze-source/references/identify-units.md +96 -110
- package/src/skf-analyze-source/references/init.md +73 -71
- package/src/skf-analyze-source/references/map-and-detect.md +123 -105
- package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
- package/src/skf-analyze-source/references/recommend.md +51 -70
- package/src/skf-analyze-source/references/scan-project.md +60 -84
- package/src/skf-analyze-source/references/scan-root.md +39 -0
- package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
- package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
- package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
- package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
- package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
- package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
- package/src/skf-audit-skill/SKILL.md +43 -51
- package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
- package/src/skf-audit-skill/customize.toml +41 -20
- package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
- package/src/skf-audit-skill/references/doc-drift.md +141 -0
- package/src/skf-audit-skill/references/headless-contract.md +29 -0
- package/src/skf-audit-skill/references/init.md +148 -210
- package/src/skf-audit-skill/references/re-index.md +96 -108
- package/src/skf-audit-skill/references/report.md +96 -33
- package/src/skf-audit-skill/references/semantic-diff.md +27 -18
- package/src/skf-audit-skill/references/severity-classify.md +79 -65
- package/src/skf-audit-skill/references/severity-rules.md +15 -6
- package/src/skf-audit-skill/references/structural-diff.md +75 -90
- package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
- package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
- package/src/skf-brief-skill/SKILL.md +36 -25
- package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
- package/src/skf-brief-skill/assets/scope-templates.md +17 -24
- package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
- package/src/skf-brief-skill/customize.toml +48 -20
- package/src/skf-brief-skill/references/analyze-target.md +142 -125
- package/src/skf-brief-skill/references/confirm-brief.md +10 -8
- package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
- package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
- package/src/skf-brief-skill/references/gather-intent.md +87 -182
- package/src/skf-brief-skill/references/headless-args.md +84 -16
- package/src/skf-brief-skill/references/health-check.md +1 -1
- package/src/skf-brief-skill/references/invocation-contract.md +18 -13
- package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
- package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
- package/src/skf-brief-skill/references/scope-definition.md +123 -47
- package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
- package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
- package/src/skf-brief-skill/references/version-resolution.md +3 -2
- package/src/skf-brief-skill/references/write-brief.md +66 -57
- package/src/skf-campaign/SKILL.md +31 -34
- package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
- package/src/skf-campaign/customize.toml +47 -16
- package/src/skf-campaign/references/campaign-contracts.md +39 -29
- package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
- package/src/skf-campaign/references/health-check.md +7 -16
- package/src/skf-campaign/references/step-01-setup.md +46 -84
- package/src/skf-campaign/references/step-02-strategy.md +31 -30
- package/src/skf-campaign/references/step-03-pins.md +26 -18
- package/src/skf-campaign/references/step-04-provenance.md +24 -23
- package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
- package/src/skf-campaign/references/step-06-batch.md +41 -34
- package/src/skf-campaign/references/step-07-capstone.md +40 -25
- package/src/skf-campaign/references/step-08-verify.md +28 -29
- package/src/skf-campaign/references/step-09-refine.md +29 -30
- package/src/skf-campaign/references/step-10-export.md +48 -36
- package/src/skf-campaign/references/step-11-maintenance.md +31 -34
- package/src/skf-campaign/references/step-resume.md +37 -68
- package/src/skf-campaign/scripts/campaign-deps.py +57 -18
- package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
- package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
- package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
- package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
- package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
- package/src/skf-campaign/scripts/campaign-report.py +204 -37
- package/src/skf-campaign/scripts/campaign-state.py +1128 -0
- package/src/skf-campaign/scripts/campaign-status.py +10 -74
- package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
- package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
- package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
- package/src/skf-campaign/templates/campaign-report-template.md +5 -1
- package/src/skf-campaign/templates/kickoff-template.md +0 -14
- package/src/skf-create-skill/SKILL.md +27 -20
- package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
- package/src/skf-create-skill/assets/skill-sections.md +15 -5
- package/src/skf-create-skill/customize.toml +20 -8
- package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
- package/src/skf-create-skill/references/batch-mode.md +81 -0
- package/src/skf-create-skill/references/ccc-index-check.md +27 -0
- package/src/skf-create-skill/references/compile.md +107 -29
- package/src/skf-create-skill/references/component-extraction.md +113 -91
- package/src/skf-create-skill/references/enrich.md +41 -23
- package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
- package/src/skf-create-skill/references/extract.md +200 -161
- package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
- package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
- package/src/skf-create-skill/references/extraction-patterns.md +584 -196
- package/src/skf-create-skill/references/generate-artifacts.md +145 -93
- package/src/skf-create-skill/references/health-check.md +1 -1
- package/src/skf-create-skill/references/load-brief.md +53 -36
- package/src/skf-create-skill/references/relabel-rule.md +12 -0
- package/src/skf-create-skill/references/report.md +28 -59
- package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
- package/src/skf-create-skill/references/step-auto-shard.md +20 -93
- package/src/skf-create-skill/references/step-doc-rot.md +44 -60
- package/src/skf-create-skill/references/step-doc-sources.md +28 -19
- package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
- package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
- package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
- package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
- package/src/skf-create-skill/references/validate.md +173 -99
- package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
- package/src/skf-create-stack-skill/SKILL.md +29 -56
- package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
- package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
- package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
- package/src/skf-create-stack-skill/customize.toml +23 -12
- package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
- package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
- package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
- package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
- package/src/skf-create-stack-skill/references/generate-output.md +138 -128
- package/src/skf-create-stack-skill/references/init.md +90 -32
- package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
- package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
- package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
- package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
- package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
- package/src/skf-create-stack-skill/references/report.md +39 -33
- package/src/skf-create-stack-skill/references/validate.md +20 -39
- package/src/skf-drop-skill/SKILL.md +28 -51
- package/src/skf-drop-skill/customize.toml +44 -30
- package/src/skf-drop-skill/references/execute.md +79 -146
- package/src/skf-drop-skill/references/invocation-contract.md +64 -0
- package/src/skf-drop-skill/references/report.md +46 -30
- package/src/skf-drop-skill/references/select.md +118 -124
- package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
- package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
- package/src/skf-export-skill/SKILL.md +40 -62
- package/src/skf-export-skill/assets/managed-section-format.md +14 -50
- package/src/skf-export-skill/assets/snippet-format.md +3 -1
- package/src/skf-export-skill/customize.toml +42 -13
- package/src/skf-export-skill/references/generate-snippet.md +54 -46
- package/src/skf-export-skill/references/health-check.md +2 -0
- package/src/skf-export-skill/references/invocation-contract.md +15 -0
- package/src/skf-export-skill/references/load-skill.md +88 -62
- package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
- package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
- package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
- package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
- package/src/skf-export-skill/references/package.md +15 -73
- package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
- package/src/skf-export-skill/references/result-envelope.md +31 -16
- package/src/skf-export-skill/references/summary.md +41 -17
- package/src/skf-export-skill/references/token-report.md +4 -12
- package/src/skf-export-skill/references/update-context.md +118 -237
- package/src/skf-forger/SKILL.md +61 -37
- package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
- package/src/skf-forger/customize.toml +43 -0
- package/src/skf-forger/references/pipeline-mode.md +73 -18
- package/src/skf-forger/scripts/forge-status.py +299 -0
- package/src/skf-forger/scripts/parse-pipeline.py +322 -54
- package/src/skf-forger/scripts/pipeline-gate.py +428 -0
- package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
- package/src/skf-quick-skill/SKILL.md +54 -53
- package/src/skf-quick-skill/assets/skill-template.md +4 -2
- package/src/skf-quick-skill/customize.toml +51 -20
- package/src/skf-quick-skill/references/batch-mode.md +74 -21
- package/src/skf-quick-skill/references/compile.md +30 -31
- package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
- package/src/skf-quick-skill/references/finalize.md +69 -18
- package/src/skf-quick-skill/references/halt-contract.md +41 -29
- package/src/skf-quick-skill/references/health-check.md +5 -3
- package/src/skf-quick-skill/references/quick-extract.md +114 -45
- package/src/skf-quick-skill/references/registry-resolution.md +9 -64
- package/src/skf-quick-skill/references/resolve-target.md +116 -66
- package/src/skf-quick-skill/references/write-and-validate.md +79 -40
- package/src/skf-refine-architecture/SKILL.md +43 -53
- package/src/skf-refine-architecture/customize.toml +49 -20
- package/src/skf-refine-architecture/references/compile.md +97 -54
- package/src/skf-refine-architecture/references/exit-codes.md +30 -0
- package/src/skf-refine-architecture/references/finding-storage.md +21 -0
- package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
- package/src/skf-refine-architecture/references/improvements.md +14 -20
- package/src/skf-refine-architecture/references/init.md +98 -57
- package/src/skf-refine-architecture/references/issue-detection.md +32 -39
- package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
- package/src/skf-refine-architecture/references/report.md +65 -54
- package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
- package/src/skf-rename-skill/SKILL.md +19 -54
- package/src/skf-rename-skill/customize.toml +32 -26
- package/src/skf-rename-skill/references/execute.md +230 -184
- package/src/skf-rename-skill/references/exit-codes.md +7 -7
- package/src/skf-rename-skill/references/health-check.md +1 -7
- package/src/skf-rename-skill/references/invocation-contract.md +25 -0
- package/src/skf-rename-skill/references/report.md +63 -52
- package/src/skf-rename-skill/references/select.md +197 -115
- package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
- package/src/skf-setup/SKILL.md +26 -26
- package/src/skf-setup/customize.toml +14 -10
- package/src/skf-setup/references/auto-index.md +50 -63
- package/src/skf-setup/references/ccc-index.md +29 -90
- package/src/skf-setup/references/detect-and-tier.md +58 -67
- package/src/skf-setup/references/health-check.md +2 -0
- package/src/skf-setup/references/invocation-contract.md +14 -0
- package/src/skf-setup/references/report.md +43 -166
- package/src/skf-setup/references/tier-rules.md +5 -5
- package/src/skf-setup/references/write-config.md +34 -57
- package/src/skf-test-skill/SKILL.md +17 -57
- package/src/skf-test-skill/assets/output-section-formats.md +33 -34
- package/src/skf-test-skill/customize.toml +27 -13
- package/src/skf-test-skill/references/coherence-check.md +134 -62
- package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
- package/src/skf-test-skill/references/coverage-check.md +217 -254
- package/src/skf-test-skill/references/detect-mode.md +5 -5
- package/src/skf-test-skill/references/external-validators.md +94 -82
- package/src/skf-test-skill/references/health-check.md +6 -7
- package/src/skf-test-skill/references/init.md +163 -88
- package/src/skf-test-skill/references/invocation-contract.md +66 -0
- package/src/skf-test-skill/references/migration-section-rules.md +5 -2
- package/src/skf-test-skill/references/report.md +162 -142
- package/src/skf-test-skill/references/score.md +80 -107
- package/src/skf-test-skill/references/scoring-rules.md +41 -155
- package/src/skf-test-skill/references/source-access-protocol.md +19 -92
- package/src/skf-test-skill/references/step-hard-gate.md +51 -33
- package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
- package/src/skf-test-skill/scripts/build-result-context.py +287 -0
- package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
- package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
- package/src/skf-test-skill/scripts/compute-score.py +223 -45
- package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
- package/src/skf-test-skill/scripts/hard-gate.py +203 -0
- package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
- package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
- package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
- package/src/skf-test-skill/scripts/score-signatures.py +459 -0
- package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
- package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
- package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
- package/src/skf-test-skill/templates/test-report-template.md +21 -15
- package/src/skf-update-skill/SKILL.md +38 -25
- package/src/skf-update-skill/customize.toml +27 -12
- package/src/skf-update-skill/references/detect-changes.md +233 -222
- package/src/skf-update-skill/references/gap-driven.md +336 -0
- package/src/skf-update-skill/references/health-check.md +31 -5
- package/src/skf-update-skill/references/init.md +250 -84
- package/src/skf-update-skill/references/invocation-contract.md +12 -0
- package/src/skf-update-skill/references/manual-section-rules.md +6 -23
- package/src/skf-update-skill/references/merge.md +180 -47
- package/src/skf-update-skill/references/re-extract.md +53 -262
- package/src/skf-update-skill/references/report.md +100 -35
- package/src/skf-update-skill/references/write.md +331 -149
- package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
- package/src/skf-verify-stack/SKILL.md +23 -42
- package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
- package/src/skf-verify-stack/customize.toml +55 -22
- package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
- package/src/skf-verify-stack/references/coverage.md +67 -64
- package/src/skf-verify-stack/references/exit-codes.md +21 -8
- package/src/skf-verify-stack/references/init.md +107 -62
- package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
- package/src/skf-verify-stack/references/integrations.md +106 -65
- package/src/skf-verify-stack/references/report.md +59 -42
- package/src/skf-verify-stack/references/requirements.md +32 -9
- package/src/skf-verify-stack/references/synthesize.md +53 -39
- package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
- package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
- package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
- package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
- package/tools/cli/commands/install.js +11 -4
- package/tools/cli/commands/status.js +15 -3
- package/tools/cli/commands/update.js +11 -4
- package/tools/cli/lib/tool-check.js +320 -0
- package/tools/cli/lib/version-check.js +22 -9
- package/tools/cli/skf-cli.js +4 -1
- package/.gitattributes +0 -16
- package/.nvmrc +0 -1
- package/docs/_internal/RELEASING.md +0 -679
- package/release-audits/v1.0.0-launch-audit.md +0 -728
- package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
- package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
- package/src/skf-campaign/manifest.yaml +0 -15
- package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
- package/src/skf-create-skill/references/ecosystem-check.md +0 -74
- package/src/skf-drop-skill/references/headless-contract.md +0 -18
- package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
- package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
- package/src/skf-update-skill/references/validate.md +0 -124
- package/tools/validate-docs-drift.js +0 -233
- package/tools/validate-docs-links.js +0 -665
|
@@ -1,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",
|
|
@@ -92,7 +116,8 @@ DETECT_OUTPUT_SCHEMA (v1):
|
|
|
92
116
|
"previous_ccc_file_count": int|null,
|
|
93
117
|
# Deterministic CCC-index freshness verdict — computed against
|
|
94
118
|
# --project-root and datetime.now(UTC) captured at detect time, so the
|
|
95
|
-
# 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.
|
|
96
121
|
"ccc_index_fresh": bool
|
|
97
122
|
},
|
|
98
123
|
"deltas": {
|
|
@@ -108,12 +133,14 @@ from __future__ import annotations
|
|
|
108
133
|
import argparse
|
|
109
134
|
import json
|
|
110
135
|
import os
|
|
136
|
+
import re
|
|
111
137
|
import shutil
|
|
112
138
|
import subprocess
|
|
113
139
|
import sys
|
|
114
140
|
import tempfile
|
|
115
141
|
from concurrent.futures import ThreadPoolExecutor
|
|
116
142
|
from datetime import datetime, timezone
|
|
143
|
+
from pathlib import Path
|
|
117
144
|
|
|
118
145
|
|
|
119
146
|
VALID_TIERS = ("Quick", "Forge", "Forge+", "Deep")
|
|
@@ -130,6 +157,13 @@ PROBE_TIMEOUT_SEC = 8 # per-tool subprocess.run timeout
|
|
|
130
157
|
# own timeout. None when unavailable.
|
|
131
158
|
_TIMEOUT_BIN = shutil.which("timeout") if os.name == "posix" else None
|
|
132
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)
|
|
133
167
|
|
|
134
168
|
|
|
135
169
|
def _die(code: int, message: str) -> None:
|
|
@@ -151,7 +185,10 @@ def _resolve_outside_cwd(command: str) -> str | None:
|
|
|
151
185
|
into CWD would execute a repo-planted shim (e.g. ast-grep.cmd). Such a
|
|
152
186
|
resolution is treated as not-found. Explicit paths supplied by callers
|
|
153
187
|
(containing a separator) are honored as-is. Keep identical to the
|
|
154
|
-
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.
|
|
155
192
|
"""
|
|
156
193
|
resolved = shutil.which(command)
|
|
157
194
|
if resolved is None:
|
|
@@ -235,18 +272,30 @@ def _first_line(text: str) -> str | None:
|
|
|
235
272
|
return None
|
|
236
273
|
|
|
237
274
|
|
|
238
|
-
def
|
|
239
|
-
|
|
275
|
+
def _probe_version(command: str) -> dict:
|
|
276
|
+
"""One `<command> --version` call: available, and the first output line."""
|
|
277
|
+
rc, stdout, _ = _run([command, "--version"])
|
|
240
278
|
if rc != 0:
|
|
241
279
|
return {"available": False, "version": None}
|
|
242
280
|
return {"available": True, "version": _first_line(stdout)}
|
|
243
281
|
|
|
244
282
|
|
|
283
|
+
def probe_ast_grep() -> dict:
|
|
284
|
+
return _probe_version("ast-grep")
|
|
285
|
+
|
|
286
|
+
|
|
245
287
|
def probe_gh_cli() -> dict:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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")
|
|
250
299
|
|
|
251
300
|
|
|
252
301
|
def probe_qmd() -> dict:
|
|
@@ -289,11 +338,94 @@ def probe_ccc() -> dict:
|
|
|
289
338
|
return {"available": True, "daemon": "error", "version": version}
|
|
290
339
|
|
|
291
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
|
+
|
|
292
348
|
def probe_security_scan(env_var: str) -> dict:
|
|
293
349
|
"""Informational only — does NOT affect tier."""
|
|
294
350
|
return {"available": bool(os.environ.get(env_var, "").strip())}
|
|
295
351
|
|
|
296
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
|
+
|
|
297
429
|
def calculate_tier(tools: dict) -> str:
|
|
298
430
|
ag = tools["ast_grep"]["available"]
|
|
299
431
|
gh = tools["gh_cli"]["available"]
|
|
@@ -310,7 +442,7 @@ def calculate_tier(tools: dict) -> str:
|
|
|
310
442
|
|
|
311
443
|
|
|
312
444
|
def suggest_valid_tier(bad_value: str) -> str | None:
|
|
313
|
-
"""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.
|
|
314
446
|
|
|
315
447
|
Two-stage match:
|
|
316
448
|
1. Case-insensitive exact match — handles `deep` / `DEEP` / `forge+` /
|
|
@@ -336,6 +468,27 @@ def suggest_valid_tier(bad_value: str) -> str | None:
|
|
|
336
468
|
return matches[0] if matches else None
|
|
337
469
|
|
|
338
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
|
+
|
|
339
492
|
def tier_prerequisites_met(tier: str, tools: dict) -> tuple[bool, list[str]]:
|
|
340
493
|
"""Return (satisfied, missing_tools) for tier-prerequisite checks.
|
|
341
494
|
|
|
@@ -405,9 +558,9 @@ def read_prior_state(prior_state_path) -> dict:
|
|
|
405
558
|
def _parse_iso_timestamp(value) -> datetime | None:
|
|
406
559
|
"""Parse an ISO 8601 timestamp into a timezone-aware datetime, or None.
|
|
407
560
|
|
|
408
|
-
Normalizes a trailing 'Z' (UTC designator) to '+00:00' because
|
|
409
|
-
`datetime.fromisoformat`
|
|
410
|
-
|
|
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
|
|
411
564
|
UTC so it can be compared against a tz-aware `now` without raising. Returns
|
|
412
565
|
None on any parse failure (non-string, empty, malformed).
|
|
413
566
|
"""
|
|
@@ -440,7 +593,7 @@ def compute_ccc_index_fresh(prior: dict, project_root, now: datetime) -> bool:
|
|
|
440
593
|
- last_indexed parses AND (now - last_indexed) <= staleness threshold
|
|
441
594
|
|
|
442
595
|
The staleness threshold defaults to 24 hours when the prior field is null
|
|
443
|
-
(
|
|
596
|
+
(the staleness default in knowledge/ccc-bridge.md). Any null/unparseable required field
|
|
444
597
|
(indexed_path, status, last_indexed), path mismatch, non-fresh status,
|
|
445
598
|
unparseable threshold, or over-threshold delta yields False.
|
|
446
599
|
"""
|
|
@@ -470,16 +623,29 @@ def compute_ccc_index_fresh(prior: dict, project_root, now: datetime) -> bool:
|
|
|
470
623
|
|
|
471
624
|
|
|
472
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
|
+
|
|
473
633
|
tools: dict = {}
|
|
474
|
-
with ThreadPoolExecutor(max_workers=
|
|
634
|
+
with ThreadPoolExecutor(max_workers=7) as ex:
|
|
475
635
|
futures = {
|
|
476
636
|
"ast_grep": ex.submit(probe_ast_grep),
|
|
477
637
|
"gh_cli": ex.submit(probe_gh_cli),
|
|
478
638
|
"qmd": ex.submit(probe_qmd),
|
|
479
639
|
"ccc": ex.submit(probe_ccc),
|
|
640
|
+
"git": ex.submit(probe_git),
|
|
641
|
+
"uv": ex.submit(probe_uv),
|
|
480
642
|
}
|
|
643
|
+
ccc_version = ex.submit(probe_ccc_version)
|
|
481
644
|
for key, fut in futures.items():
|
|
482
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())
|
|
483
649
|
tools["security_scan"] = probe_security_scan(args.snyk_env_var)
|
|
484
650
|
|
|
485
651
|
detected = calculate_tier(tools)
|
|
@@ -514,8 +680,6 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
514
680
|
require_satisfied: bool | None
|
|
515
681
|
require_missing: list[str] = []
|
|
516
682
|
if args.require_tier is not None:
|
|
517
|
-
if args.require_tier not in VALID_TIERS:
|
|
518
|
-
_die(1, f"--require-tier must be one of {VALID_TIERS}, got {args.require_tier!r}")
|
|
519
683
|
require_satisfied, require_missing = tier_prerequisites_met(args.require_tier, tools)
|
|
520
684
|
else:
|
|
521
685
|
require_satisfied = None
|
|
@@ -528,6 +692,7 @@ def detect(args: argparse.Namespace) -> dict:
|
|
|
528
692
|
|
|
529
693
|
return {
|
|
530
694
|
"tools": tools,
|
|
695
|
+
"tools_below_minimum": tools_below_minimum,
|
|
531
696
|
"tier": {
|
|
532
697
|
"calculated": calculated,
|
|
533
698
|
"detected": detected,
|
|
@@ -595,9 +760,10 @@ def main() -> None:
|
|
|
595
760
|
"--require-tier",
|
|
596
761
|
default=None,
|
|
597
762
|
help="Require the calculated tier to satisfy this requirement (uses tool-prerequisite"
|
|
598
|
-
" check, not tier-name comparison
|
|
763
|
+
" check, not tier-name comparison: Deep does not subsume Forge+ because Deep"
|
|
599
764
|
" does not require ccc). Output reports satisfied/missing-tools; caller decides"
|
|
600
|
-
" 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.",
|
|
601
767
|
)
|
|
602
768
|
parser.add_argument(
|
|
603
769
|
"--snyk-env-var",
|
|
@@ -629,5 +795,21 @@ def main() -> None:
|
|
|
629
795
|
_ok(payload)
|
|
630
796
|
|
|
631
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
|
+
|
|
632
813
|
if __name__ == "__main__":
|
|
814
|
+
_force_utf8(sys.stdout, sys.stderr)
|
|
633
815
|
main()
|