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.
Files changed (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +49 -49
  3. package/docs/404.md +10 -8
  4. package/docs/_data/pinned.yaml +22 -13
  5. package/docs/_internal/STABILITY.md +58 -35
  6. package/docs/agents.md +39 -53
  7. package/docs/architecture.md +65 -51
  8. package/docs/bmad-synergy.md +53 -51
  9. package/docs/campaign.md +127 -75
  10. package/docs/concepts.md +34 -28
  11. package/docs/examples.md +70 -68
  12. package/docs/forge-auto.md +34 -31
  13. package/docs/getting-started.md +118 -57
  14. package/docs/how-it-works.md +25 -21
  15. package/docs/index.md +15 -15
  16. package/docs/skill-model.md +70 -59
  17. package/docs/troubleshooting.md +135 -18
  18. package/docs/verifying-a-skill.md +83 -57
  19. package/docs/why-skf.md +24 -22
  20. package/docs/workflows.md +274 -110
  21. package/package.json +18 -10
  22. package/src/forger/preferences.yaml +8 -1
  23. package/src/knowledge/agentskills-spec.md +2 -0
  24. package/src/knowledge/ccc-bridge.md +40 -24
  25. package/src/knowledge/confidence-tiers.md +9 -5
  26. package/src/knowledge/overview.md +3 -3
  27. package/src/knowledge/progressive-capability.md +8 -8
  28. package/src/knowledge/provenance-tracking.md +9 -5
  29. package/src/knowledge/qmd-registry.md +2 -2
  30. package/src/knowledge/skf-knowledge-index.csv +2 -2
  31. package/src/knowledge/skill-lifecycle.md +18 -11
  32. package/src/knowledge/split-body-strategy.md +0 -3
  33. package/src/knowledge/tool-resolution.md +4 -2
  34. package/src/knowledge/version-paths.md +101 -15
  35. package/src/module-help.csv +3 -3
  36. package/src/module.yaml +10 -0
  37. package/src/shared/data/ast-grep-recipes.yaml +608 -0
  38. package/src/shared/data/ide-context-files.json +30 -0
  39. package/src/shared/health-check.md +32 -12
  40. package/src/shared/references/description-guard-protocol.md +12 -9
  41. package/src/shared/references/feasibility-report-schema.md +19 -13
  42. package/src/shared/references/headless-gate-convention.md +55 -3
  43. package/src/shared/references/output-contract-schema.md +19 -8
  44. package/src/shared/references/pipeline-contracts.md +94 -29
  45. package/src/shared/references/tessl-review.md +77 -0
  46. package/src/shared/scripts/schemas/skf-analyze-result-envelope.v1.json +194 -0
  47. package/src/shared/scripts/schemas/skf-audit-result-envelope.v1.json +192 -0
  48. package/src/shared/scripts/schemas/skf-brief-result-envelope.v1.json +32 -4
  49. package/src/shared/scripts/schemas/skf-campaign-result-envelope.v1.json +174 -0
  50. package/src/shared/scripts/schemas/skf-create-skill-result-envelope.v1.json +284 -0
  51. package/src/shared/scripts/schemas/skf-drop-skill-result-envelope.v1.json +164 -0
  52. package/src/shared/scripts/schemas/skf-export-result-envelope.v1.json +160 -0
  53. package/src/shared/scripts/schemas/skf-quick-skill-result-envelope.v1.json +261 -0
  54. package/src/shared/scripts/schemas/skf-refine-architecture-result-envelope.v1.json +192 -0
  55. package/src/shared/scripts/schemas/skf-rename-skill-result-envelope.v1.json +179 -0
  56. package/src/shared/scripts/schemas/skf-setup-result-envelope.v1.json +27 -11
  57. package/src/shared/scripts/schemas/skf-stack-result-envelope.v1.json +176 -0
  58. package/src/shared/scripts/schemas/skf-test-result-envelope.v1.json +173 -0
  59. package/src/shared/scripts/schemas/skf-update-result-envelope.v1.json +19 -5
  60. package/src/shared/scripts/schemas/skf-verify-stack-result-envelope.v1.json +180 -0
  61. package/src/shared/scripts/schemas/skill-brief.v1.json +34 -0
  62. package/src/shared/scripts/schemas/workspace-detection.v1.json +1 -1
  63. package/src/shared/scripts/skf-atomic-write.py +30 -14
  64. package/src/shared/scripts/skf-build-change-manifest.py +2203 -24
  65. package/src/shared/scripts/skf-ccc-git-hygiene.py +532 -0
  66. package/src/shared/scripts/skf-check-unit-records.py +184 -0
  67. package/src/shared/scripts/skf-check-workspace-drift.py +538 -9
  68. package/src/shared/scripts/skf-classify-changed-files.py +694 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +1 -1
  71. package/src/shared/scripts/skf-count-imports.py +1867 -0
  72. package/src/shared/scripts/skf-count-tokens.py +1 -1
  73. package/src/shared/scripts/skf-derive-assembly-shape.py +3 -2
  74. package/src/shared/scripts/skf-description-guard.py +99 -11
  75. package/src/shared/scripts/skf-detect-docs.py +694 -2
  76. package/src/shared/scripts/skf-detect-language.py +486 -107
  77. package/src/shared/scripts/skf-detect-registry.py +887 -0
  78. package/src/shared/scripts/skf-detect-scripts-assets.py +1 -1
  79. package/src/shared/scripts/skf-detect-tools.py +209 -27
  80. package/src/shared/scripts/skf-detect-workspaces.py +506 -30
  81. package/src/shared/scripts/skf-disqualify-candidates.py +821 -182
  82. package/src/shared/scripts/skf-emit-brief-result-envelope.py +69 -191
  83. package/src/shared/scripts/skf-emit-result-envelope.py +1604 -106
  84. package/src/shared/scripts/skf-enumerate-stack-skills.py +986 -117
  85. package/src/shared/scripts/skf-extract-public-api.py +4084 -85
  86. package/src/shared/scripts/skf-extraction-inventory.py +746 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +539 -0
  88. package/src/shared/scripts/skf-fetch-temporal.py +674 -0
  89. package/src/shared/scripts/skf-find-cycles.py +97 -29
  90. package/src/shared/scripts/skf-find-test-report.py +476 -0
  91. package/src/shared/scripts/skf-forge-tier-rw.py +444 -203
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +661 -0
  94. package/src/shared/scripts/skf-hash-content.py +166 -12
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +159 -27
  97. package/src/shared/scripts/skf-manifest-ops.py +1 -1
  98. package/src/shared/scripts/skf-merge-ccc-exclusions.py +2307 -223
  99. package/src/shared/scripts/skf-merge-doc-urls.py +1 -1
  100. package/src/shared/scripts/skf-names-present.py +221 -0
  101. package/src/shared/scripts/skf-pair-intersect.py +119 -28
  102. package/src/shared/scripts/skf-parse-gaps.py +1384 -0
  103. package/src/shared/scripts/skf-preapply.py +1 -1
  104. package/src/shared/scripts/skf-preflight.py +161 -27
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +39 -7
  107. package/src/shared/scripts/skf-qmd-classify-collections.py +229 -45
  108. package/src/shared/scripts/skf-quick-batch.py +774 -0
  109. package/src/shared/scripts/skf-rebuild-managed-sections.py +972 -88
  110. package/src/shared/scripts/skf-recommend-scope-type.py +323 -156
  111. package/src/shared/scripts/skf-render-metadata-stats.py +156 -12
  112. package/src/shared/scripts/skf-render-quick-metadata.py +132 -28
  113. package/src/shared/scripts/skf-render-stack-metadata.py +976 -0
  114. package/src/shared/scripts/skf-resolve-authoritative-files.py +128 -13
  115. package/src/shared/scripts/skf-resolve-package.py +523 -106
  116. package/src/shared/scripts/skf-rewrite-skill-name.py +433 -47
  117. package/src/shared/scripts/skf-run-lock.py +577 -0
  118. package/src/shared/scripts/skf-scan-manifests.py +998 -57
  119. package/src/shared/scripts/skf-scan-skill-md-structure.py +1341 -37
  120. package/src/shared/scripts/skf-severity-classify.py +712 -108
  121. package/src/shared/scripts/skf-shape-detect.py +298 -204
  122. package/src/shared/scripts/skf-shard-body.py +9 -8
  123. package/src/shared/scripts/skf-skill-inventory.py +2412 -89
  124. package/src/shared/scripts/skf-skills-module.py +472 -0
  125. package/src/shared/scripts/skf-source-tree.py +1796 -0
  126. package/src/shared/scripts/skf-structural-diff.py +670 -119
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +5 -5
  129. package/src/shared/scripts/skf-update-run-state.py +624 -0
  130. package/src/shared/scripts/skf-validate-brief-inputs.py +92 -29
  131. package/src/shared/scripts/skf-validate-brief-schema.py +4 -1
  132. package/src/shared/scripts/skf-validate-feasibility-report.py +577 -66
  133. package/src/shared/scripts/skf-validate-frontmatter.py +103 -26
  134. package/src/shared/scripts/skf-validate-output.py +712 -64
  135. package/src/shared/scripts/skf-validate-pins.py +1 -1
  136. package/src/shared/scripts/skf-verify-no-trace.py +116 -10
  137. package/src/shared/scripts/skf-verify-provenance-completeness.py +2919 -54
  138. package/src/shared/scripts/skf-write-skill-brief.py +367 -19
  139. package/src/shared/tool-requirements.yaml +201 -0
  140. package/src/skf-analyze-source/SKILL.md +41 -36
  141. package/src/skf-analyze-source/assets/skill-brief-schema.md +7 -8
  142. package/src/skf-analyze-source/customize.toml +36 -24
  143. package/src/skf-analyze-source/references/auto-docs-only.md +58 -20
  144. package/src/skf-analyze-source/references/continue.md +2 -13
  145. package/src/skf-analyze-source/references/discover-additional-source.md +147 -0
  146. package/src/skf-analyze-source/references/generate-briefs.md +121 -55
  147. package/src/skf-analyze-source/references/headless-contract.md +42 -15
  148. package/src/skf-analyze-source/references/identify-units.md +96 -110
  149. package/src/skf-analyze-source/references/init.md +73 -71
  150. package/src/skf-analyze-source/references/map-and-detect.md +123 -105
  151. package/src/skf-analyze-source/references/map-unit-exports.md +68 -0
  152. package/src/skf-analyze-source/references/recommend.md +51 -70
  153. package/src/skf-analyze-source/references/scan-project.md +60 -84
  154. package/src/skf-analyze-source/references/scan-root.md +39 -0
  155. package/src/skf-analyze-source/references/step-auto-scope-coexistence.md +47 -0
  156. package/src/skf-analyze-source/references/step-auto-scope-corpora.md +53 -0
  157. package/src/skf-analyze-source/references/step-auto-scope-split.md +91 -0
  158. package/src/skf-analyze-source/references/step-auto-scope.md +208 -293
  159. package/src/skf-analyze-source/references/step-shape-detect.md +14 -12
  160. package/src/skf-analyze-source/references/unit-detection-heuristics.md +37 -42
  161. package/src/skf-audit-skill/SKILL.md +43 -51
  162. package/src/skf-audit-skill/assets/drift-report-template.md +27 -3
  163. package/src/skf-audit-skill/customize.toml +41 -20
  164. package/src/skf-audit-skill/references/constituent-freshness.md +91 -0
  165. package/src/skf-audit-skill/references/doc-drift.md +141 -0
  166. package/src/skf-audit-skill/references/headless-contract.md +29 -0
  167. package/src/skf-audit-skill/references/init.md +148 -210
  168. package/src/skf-audit-skill/references/re-index.md +96 -108
  169. package/src/skf-audit-skill/references/report.md +96 -33
  170. package/src/skf-audit-skill/references/semantic-diff.md +27 -18
  171. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  172. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  173. package/src/skf-audit-skill/references/structural-diff.md +75 -90
  174. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  175. package/src/skf-audit-skill/scripts/render-drift-tables.py +685 -0
  176. package/src/skf-brief-skill/SKILL.md +36 -25
  177. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  178. package/src/skf-brief-skill/assets/scope-templates.md +17 -24
  179. package/src/skf-brief-skill/assets/skill-brief-schema.md +29 -15
  180. package/src/skf-brief-skill/customize.toml +48 -20
  181. package/src/skf-brief-skill/references/analyze-target.md +142 -125
  182. package/src/skf-brief-skill/references/confirm-brief.md +10 -8
  183. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  184. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  185. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  186. package/src/skf-brief-skill/references/headless-args.md +84 -16
  187. package/src/skf-brief-skill/references/health-check.md +1 -1
  188. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  189. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  190. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  191. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  192. package/src/skf-brief-skill/references/step-auto-brief.md +24 -80
  193. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  194. package/src/skf-brief-skill/references/version-resolution.md +3 -2
  195. package/src/skf-brief-skill/references/write-brief.md +66 -57
  196. package/src/skf-campaign/SKILL.md +31 -34
  197. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  198. package/src/skf-campaign/customize.toml +47 -16
  199. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  200. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  201. package/src/skf-campaign/references/health-check.md +7 -16
  202. package/src/skf-campaign/references/step-01-setup.md +46 -84
  203. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  204. package/src/skf-campaign/references/step-03-pins.md +26 -18
  205. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  206. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  207. package/src/skf-campaign/references/step-06-batch.md +41 -34
  208. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  209. package/src/skf-campaign/references/step-08-verify.md +28 -29
  210. package/src/skf-campaign/references/step-09-refine.md +29 -30
  211. package/src/skf-campaign/references/step-10-export.md +48 -36
  212. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  213. package/src/skf-campaign/references/step-resume.md +37 -68
  214. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  215. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  216. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  217. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  218. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  219. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  220. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  221. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  222. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  223. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  224. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  225. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  226. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  227. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  228. package/src/skf-create-skill/SKILL.md +27 -20
  229. package/src/skf-create-skill/assets/compile-assembly-rules.md +6 -5
  230. package/src/skf-create-skill/assets/skill-sections.md +15 -5
  231. package/src/skf-create-skill/customize.toml +20 -8
  232. package/src/skf-create-skill/references/authoritative-files-protocol.md +41 -42
  233. package/src/skf-create-skill/references/batch-mode.md +81 -0
  234. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  235. package/src/skf-create-skill/references/compile.md +107 -29
  236. package/src/skf-create-skill/references/component-extraction.md +113 -91
  237. package/src/skf-create-skill/references/enrich.md +41 -23
  238. package/src/skf-create-skill/references/entry-points-by-hand.md +20 -0
  239. package/src/skf-create-skill/references/extract.md +200 -161
  240. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  241. package/src/skf-create-skill/references/extraction-patterns-tracing.md +2 -2
  242. package/src/skf-create-skill/references/extraction-patterns.md +584 -196
  243. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  244. package/src/skf-create-skill/references/health-check.md +1 -1
  245. package/src/skf-create-skill/references/load-brief.md +53 -36
  246. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  247. package/src/skf-create-skill/references/report.md +28 -59
  248. package/src/skf-create-skill/references/source-resolution-protocols.md +49 -155
  249. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  250. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  251. package/src/skf-create-skill/references/step-doc-sources.md +28 -19
  252. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  253. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
  254. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  255. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  256. package/src/skf-create-skill/references/validate.md +173 -99
  257. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  258. package/src/skf-create-stack-skill/SKILL.md +29 -56
  259. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  260. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  261. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  262. package/src/skf-create-stack-skill/customize.toml +23 -12
  263. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  264. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  265. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  266. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  267. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  268. package/src/skf-create-stack-skill/references/init.md +90 -32
  269. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  270. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  271. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  272. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  273. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  274. package/src/skf-create-stack-skill/references/report.md +39 -33
  275. package/src/skf-create-stack-skill/references/validate.md +20 -39
  276. package/src/skf-drop-skill/SKILL.md +28 -51
  277. package/src/skf-drop-skill/customize.toml +44 -30
  278. package/src/skf-drop-skill/references/execute.md +79 -146
  279. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  280. package/src/skf-drop-skill/references/report.md +46 -30
  281. package/src/skf-drop-skill/references/select.md +118 -124
  282. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  283. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  284. package/src/skf-export-skill/SKILL.md +40 -62
  285. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  286. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  287. package/src/skf-export-skill/customize.toml +42 -13
  288. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  289. package/src/skf-export-skill/references/health-check.md +2 -0
  290. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  291. package/src/skf-export-skill/references/load-skill.md +88 -62
  292. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  293. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  294. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  295. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  296. package/src/skf-export-skill/references/package.md +15 -73
  297. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  298. package/src/skf-export-skill/references/result-envelope.md +31 -16
  299. package/src/skf-export-skill/references/summary.md +41 -17
  300. package/src/skf-export-skill/references/token-report.md +4 -12
  301. package/src/skf-export-skill/references/update-context.md +118 -237
  302. package/src/skf-forger/SKILL.md +61 -37
  303. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  304. package/src/skf-forger/customize.toml +43 -0
  305. package/src/skf-forger/references/pipeline-mode.md +73 -18
  306. package/src/skf-forger/scripts/forge-status.py +299 -0
  307. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  308. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  309. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  310. package/src/skf-quick-skill/SKILL.md +54 -53
  311. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  312. package/src/skf-quick-skill/customize.toml +51 -20
  313. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  314. package/src/skf-quick-skill/references/compile.md +30 -31
  315. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  316. package/src/skf-quick-skill/references/finalize.md +69 -18
  317. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  318. package/src/skf-quick-skill/references/health-check.md +5 -3
  319. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  320. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  321. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  322. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  323. package/src/skf-refine-architecture/SKILL.md +43 -53
  324. package/src/skf-refine-architecture/customize.toml +49 -20
  325. package/src/skf-refine-architecture/references/compile.md +97 -54
  326. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  327. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  328. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  329. package/src/skf-refine-architecture/references/improvements.md +14 -20
  330. package/src/skf-refine-architecture/references/init.md +98 -57
  331. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  332. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  333. package/src/skf-refine-architecture/references/report.md +65 -54
  334. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  335. package/src/skf-rename-skill/SKILL.md +19 -54
  336. package/src/skf-rename-skill/customize.toml +32 -26
  337. package/src/skf-rename-skill/references/execute.md +230 -184
  338. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  339. package/src/skf-rename-skill/references/health-check.md +1 -7
  340. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  341. package/src/skf-rename-skill/references/report.md +63 -52
  342. package/src/skf-rename-skill/references/select.md +197 -115
  343. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  344. package/src/skf-setup/SKILL.md +26 -26
  345. package/src/skf-setup/customize.toml +14 -10
  346. package/src/skf-setup/references/auto-index.md +50 -63
  347. package/src/skf-setup/references/ccc-index.md +29 -90
  348. package/src/skf-setup/references/detect-and-tier.md +58 -67
  349. package/src/skf-setup/references/health-check.md +2 -0
  350. package/src/skf-setup/references/invocation-contract.md +14 -0
  351. package/src/skf-setup/references/report.md +43 -166
  352. package/src/skf-setup/references/tier-rules.md +5 -5
  353. package/src/skf-setup/references/write-config.md +34 -57
  354. package/src/skf-test-skill/SKILL.md +17 -57
  355. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  356. package/src/skf-test-skill/customize.toml +27 -13
  357. package/src/skf-test-skill/references/coherence-check.md +134 -62
  358. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  359. package/src/skf-test-skill/references/coverage-check.md +217 -254
  360. package/src/skf-test-skill/references/detect-mode.md +5 -5
  361. package/src/skf-test-skill/references/external-validators.md +94 -82
  362. package/src/skf-test-skill/references/health-check.md +6 -7
  363. package/src/skf-test-skill/references/init.md +163 -88
  364. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  365. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  366. package/src/skf-test-skill/references/report.md +162 -142
  367. package/src/skf-test-skill/references/score.md +80 -107
  368. package/src/skf-test-skill/references/scoring-rules.md +41 -155
  369. package/src/skf-test-skill/references/source-access-protocol.md +19 -92
  370. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  371. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  372. package/src/skf-test-skill/scripts/build-result-context.py +287 -0
  373. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  374. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  375. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  376. package/src/skf-test-skill/scripts/gap-ledger.py +1381 -0
  377. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  378. package/src/skf-test-skill/scripts/load-coverage-inputs.py +760 -0
  379. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  380. package/src/skf-test-skill/scripts/reconcile-coverage.py +273 -79
  381. package/src/skf-test-skill/scripts/score-signatures.py +459 -0
  382. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  383. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  384. package/src/skf-test-skill/scripts/verify-declared-numerator.py +127 -52
  385. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  386. package/src/skf-update-skill/SKILL.md +38 -25
  387. package/src/skf-update-skill/customize.toml +27 -12
  388. package/src/skf-update-skill/references/detect-changes.md +233 -222
  389. package/src/skf-update-skill/references/gap-driven.md +336 -0
  390. package/src/skf-update-skill/references/health-check.md +31 -5
  391. package/src/skf-update-skill/references/init.md +250 -84
  392. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  393. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  394. package/src/skf-update-skill/references/merge.md +180 -47
  395. package/src/skf-update-skill/references/re-extract.md +53 -262
  396. package/src/skf-update-skill/references/report.md +100 -35
  397. package/src/skf-update-skill/references/write.md +331 -149
  398. package/src/skf-update-skill/scripts/skf-new-file-diff.py +29 -7
  399. package/src/skf-verify-stack/SKILL.md +23 -42
  400. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  401. package/src/skf-verify-stack/customize.toml +55 -22
  402. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  403. package/src/skf-verify-stack/references/coverage.md +67 -64
  404. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  405. package/src/skf-verify-stack/references/init.md +107 -62
  406. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  407. package/src/skf-verify-stack/references/integrations.md +106 -65
  408. package/src/skf-verify-stack/references/report.md +59 -42
  409. package/src/skf-verify-stack/references/requirements.md +32 -9
  410. package/src/skf-verify-stack/references/synthesize.md +53 -39
  411. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  412. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  413. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  414. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  415. package/tools/cli/commands/install.js +11 -4
  416. package/tools/cli/commands/status.js +15 -3
  417. package/tools/cli/commands/update.js +11 -4
  418. package/tools/cli/lib/tool-check.js +320 -0
  419. package/tools/cli/lib/version-check.js +22 -9
  420. package/tools/cli/skf-cli.js +4 -1
  421. package/.gitattributes +0 -16
  422. package/.nvmrc +0 -1
  423. package/docs/_internal/RELEASING.md +0 -679
  424. package/release-audits/v1.0.0-launch-audit.md +0 -728
  425. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  426. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  427. package/src/skf-campaign/manifest.yaml +0 -15
  428. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  429. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  430. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  431. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  432. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  433. package/src/skf-update-skill/references/validate.md +0 -124
  434. package/tools/validate-docs-drift.js +0 -233
  435. package/tools/validate-docs-links.js +0 -665
@@ -1,5 +1,5 @@
1
1
  # /// script
2
- # requires-python = ">=3.10"
2
+ # requires-python = ">=3.11"
3
3
  # dependencies = []
4
4
  # ///
5
5
  """SKF Detect Scripts & Assets — file-level artifact detection for skill compilation.
@@ -1,19 +1,32 @@
1
1
  # /// script
2
- # requires-python = ">=3.10"
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 ccc concurrently, applies the 4-rule tier decision table (see
10
- `src/skf-setup/references/tier-rules.md`), evaluates --tier-override (with
11
- sanity check) and --require-tier (with tool-prerequisite check independent of
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 (status report + envelope).
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
- Bare `python3` works when dependencies = [] (this script's case) but
51
- becomes brittle the moment a non-stdlib dep is added — prefer `uv run`
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 branches on a boolean instead of doing timestamp math.
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 guard in skf-qmd-classify-collections.py.
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 probe_ast_grep() -> dict:
239
- rc, stdout, _ = _run(["ast-grep", "--version"])
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
- rc, stdout, _ = _run(["gh", "--version"])
247
- if rc != 0:
248
- return {"available": False, "version": None}
249
- return {"available": True, "version": _first_line(stdout)}
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` does not accept 'Z' on Python < 3.11 and
410
- requires-python here is >=3.10. A naive result (no tzinfo) is assumed to be
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
- (matching the ccc-index.md §2 default). Any null/unparseable required field
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=4) as ex:
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 — Deep does not subsume Forge+ because Deep"
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()