bmad-module-skill-forge 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (435) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/README.md +51 -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 +14 -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 +117 -9
  75. package/src/shared/scripts/skf-detect-docs.py +892 -4
  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 +215 -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 +17 -6
  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 +55 -18
  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 -57
  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 -89
  348. package/src/skf-setup/references/detect-and-tier.md +58 -66
  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 -645
  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
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """Deterministic circuit-breaker decision for one finished pipeline step.
7
+
8
+ Pipeline Mode (references/pipeline-mode.md step 4d) runs this after AN, TS, AS
9
+ or VS finishes, instead of reading the step's result and comparing it by
10
+ hand. Which field carries the verdict, and which verdicts go on, skip or stop,
11
+ has one correct answer per result, so it runs here, not in the prompt. The
12
+ gate never compares a TS score with a threshold: the forger hands a `[min:N]`
13
+ to TS as `--threshold=N`, and TS settles its own verdict, caps and 80% floor
14
+ fallback included.
15
+
16
+ The rules mirror the Circuit Breakers table in
17
+ `src/shared/references/pipeline-contracts.md`:
18
+
19
+ TS continue only on the settled verdict PASS, which TS routes to export
20
+ (`next_workflow` is `export-skill`). FAIL, INCONCLUSIVE and
21
+ pass-with-drift halt with the verdict as the reason; so does a FAIL
22
+ that a post-score cap forced while the score clears the threshold.
23
+ AN continue when it confirmed at least `--min` skillable units (default 1).
24
+ A redirect or a skipped target halts: there is no brief to go on with.
25
+ AS CRITICAL drift halts; CLEAN skips an update that comes next
26
+ (`--next US`) unless AS routes to it (`next_workflow` is
27
+ `update-skill`: upstream moved past the skill's ref, so an update is
28
+ due however clean the audited tree read); MINOR and SIGNIFICANT
29
+ continue.
30
+ VS zero coverage halts (`zero-coverage`): VS verified none of the
31
+ architecture's technologies, so nothing after it has evidence to use.
32
+ Every verdict with coverage continues, NOT_FEASIBLE included: RA, which
33
+ usually follows VS, takes each Blocked integration as a critical issue.
34
+
35
+ A result with `status` `error` or `failed` halts with its `halt_reason`, or
36
+ its status when it names none, a TS PASS included. A TS result with any other
37
+ verdict keeps the verdict as the reason, so a hard-gate block halts as FAIL. A
38
+ result the gate cannot read, or a value outside its rule's set, halts too: a
39
+ gate that cannot decide never lets a step through.
40
+
41
+ Input, on stdin (or in the file `--result` names, for tests and CI): the
42
+ step's result envelope line (`SKF_TEST_RESULT_JSON: {...}` and the like; the
43
+ last line of the code's own envelope wins, and one from another workflow is
44
+ refused) or a result record. Pipeline Mode gates only the envelope line the
45
+ step just printed, and an empty input when it printed none (`no-result`):
46
+ a `-latest.json` record may not describe this run, because a workflow can
47
+ halt after writing it (TS writes its record before its last halts) or abort
48
+ before writing one. The envelope and the record name the fields differently,
49
+ and both are read:
50
+
51
+ TS verdict `verdict` | `summary.result` (PASS_WITH_DRIFT reads as
52
+ pass-with-drift), route `next_workflow` (checked when present)
53
+ AN units `unit_counts.confirmed`, else the `brief_paths`, else the
54
+ `skill-brief.yaml` paths in `outputs[]`
55
+ AS severity `drift_score` | `summary.severity`, route `next_workflow` |
56
+ `summary.next_workflow`, handoff `upstream_moved` and `upstream_ref`
57
+ (or their `summary.` copies)
58
+ VS verdict `overall_verdict` | `summary.overallVerdict`, coverage
59
+ `coverage_percentage` | `summary.coveragePercentage`
60
+
61
+ The input is read as UTF-8 and a byte-order mark is dropped; input that is
62
+ not UTF-8 halts as `result-unreadable`.
63
+
64
+ CLI usage:
65
+ uv run scripts/pipeline-gate.py --code TS --next EX <<'SKF_RESULT'
66
+ SKF_TEST_RESULT_JSON: {"status":"success","verdict":"FAIL",...}
67
+ SKF_RESULT
68
+ uv run scripts/pipeline-gate.py --code AN --min 2 <<'SKF_RESULT'
69
+ SKF_RESULT
70
+ uv run scripts/pipeline-gate.py --code AS --next US --result <saved-envelope.txt>
71
+
72
+ (The AN call has an empty heredoc: the step printed no envelope, so the
73
+ gate halts with `no-result`.)
74
+
75
+ Output (stdout, one object):
76
+ {
77
+ "code": "TS",
78
+ "decision": "continue" | "skip" | "halt",
79
+ "reason": null | "<why it skips or halts>", # TS: FAIL, INCONCLUSIVE, pass-with-drift
80
+ "skip": null | "US", # the next workflow a skip passes over
81
+ "message": "<one line for the pipeline report>",
82
+ "handoff": null | {"target_ref": "<ref>"} # what the step hands the next, by Data Flow name
83
+ }
84
+
85
+ `handoff` is set only after an AS whose result says upstream moved
86
+ (`upstream_moved` true) and names the new ref (`upstream_ref`): a continue,
87
+ or a CRITICAL halt, whose review-drift-report repair picks the chain up at
88
+ US. `pipeline-journal.py step --gate` records it, and US takes it as
89
+ `--target-ref`, so the update reads the ref the audit found.
90
+
91
+ Exit codes:
92
+ 0 a decision was printed (continue, skip or halt: read `decision`)
93
+ 2 usage error (argparse: a missing or unknown argument, usage on stderr,
94
+ no JSON)
95
+ """
96
+
97
+ from __future__ import annotations
98
+
99
+ import argparse
100
+ import json
101
+ import re
102
+ import sys
103
+ from pathlib import Path
104
+
105
+ # The envelope each gated workflow prints in headless mode.
106
+ ENVELOPES = {
107
+ "AN": "SKF_ANALYZE_RESULT_JSON",
108
+ "TS": "SKF_TEST_RESULT_JSON",
109
+ "AS": "SKF_AUDIT_RESULT_JSON",
110
+ "VS": "SKF_VERIFY_STACK_RESULT_JSON",
111
+ }
112
+ GATED_CODES = tuple(ENVELOPES)
113
+
114
+ # Settled test-skill verdicts, keyed by their normalized spelling; the value
115
+ # is the envelope's spelling, which a halt reports as its reason.
116
+ TS_VERDICTS = {
117
+ "PASS": "PASS",
118
+ "FAIL": "FAIL",
119
+ "INCONCLUSIVE": "INCONCLUSIVE",
120
+ "PASS_WITH_DRIFT": "pass-with-drift",
121
+ }
122
+ AS_SEVERITIES = ("CLEAN", "MINOR", "SIGNIFICANT", "CRITICAL")
123
+ VS_VERDICTS = ("FEASIBLE", "CONDITIONALLY_FEASIBLE", "NOT_FEASIBLE")
124
+ ERROR_STATUSES = frozenset({"error", "failed"})
125
+ EXPORT_ROUTE = "export-skill"
126
+ UPDATE_ROUTE = "update-skill"
127
+
128
+ _ENVELOPE_RE = re.compile(r"(SKF_[A-Z0-9_]+_RESULT_JSON):\s*(\{.*\})\s*$")
129
+ _MISSING = object()
130
+
131
+
132
+ # --------------------------------------------------------------------------
133
+ # Reading the result
134
+ # --------------------------------------------------------------------------
135
+
136
+
137
+ def read_result(code: str, text: str | None) -> tuple[dict | None, str | None]:
138
+ """Return (result, problem) for the text a step produced.
139
+
140
+ The last envelope line of `code`'s own workflow wins. Envelope lines of
141
+ other workflows alone are refused (`wrong-result`); with no envelope line
142
+ at all, the whole text must be one JSON object (a result record).
143
+ """
144
+ if text is None:
145
+ return None, "result-unreadable"
146
+ if not text.strip():
147
+ return None, "no-result"
148
+ found = [m for m in map(_ENVELOPE_RE.search, text.splitlines()) if m]
149
+ own = [m.group(2) for m in found if m.group(1) == ENVELOPES[code]]
150
+ if found and not own:
151
+ return None, "wrong-result"
152
+ try:
153
+ value = json.loads(own[-1] if own else text)
154
+ except json.JSONDecodeError:
155
+ return None, "result-unreadable"
156
+ if not isinstance(value, dict):
157
+ return None, "result-unreadable"
158
+ return value, None
159
+
160
+
161
+ def _field(result: dict, key: str, summary_key: str):
162
+ """The envelope's `key`, else the record's `summary.<summary_key>`."""
163
+ value = result.get(key)
164
+ if value is None:
165
+ summary = result.get("summary")
166
+ if isinstance(summary, dict):
167
+ value = summary.get(summary_key)
168
+ return value
169
+
170
+
171
+ def _token(value) -> str | None:
172
+ """A verdict or severity as one upper-case token: `pass-with-drift` -> PASS_WITH_DRIFT."""
173
+ if not isinstance(value, str) or not value.strip():
174
+ return None
175
+ return value.strip().upper().replace("-", "_")
176
+
177
+
178
+ def _is_error(result: dict) -> bool:
179
+ status = result.get("status")
180
+ return isinstance(status, str) and status.strip().lower() in ERROR_STATUSES
181
+
182
+
183
+ def _halt_reason(result: dict) -> str:
184
+ """The reason an errored result names, or its status."""
185
+ summary = result.get("summary") if isinstance(result.get("summary"), dict) else {}
186
+ for value in (result.get("halt_reason"), summary.get("halt_reason")):
187
+ if isinstance(value, str) and value.strip():
188
+ return value.strip()
189
+ return str(result.get("status")).strip().lower()
190
+
191
+
192
+ def _count(value) -> int | None:
193
+ if isinstance(value, bool) or not isinstance(value, int):
194
+ return None
195
+ return value
196
+
197
+
198
+ def _percentage(value) -> int | float | None:
199
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
200
+ return None
201
+ return value if 0 <= value <= 100 else None
202
+
203
+
204
+ def _units(result: dict) -> int | None:
205
+ """Confirmed skillable units: the envelope count, else the briefs listed."""
206
+ counts = result.get("unit_counts")
207
+ if isinstance(counts, dict) and _count(counts.get("confirmed")) is not None:
208
+ return counts["confirmed"]
209
+ briefs = result.get("brief_paths")
210
+ if isinstance(briefs, list):
211
+ return len(briefs)
212
+ outputs = result.get("outputs")
213
+ if isinstance(outputs, list):
214
+ paths = [o.get("path") if isinstance(o, dict) else o for o in outputs]
215
+ return sum(isinstance(p, str) and p.endswith("skill-brief.yaml") for p in paths)
216
+ return None
217
+
218
+
219
+ # --------------------------------------------------------------------------
220
+ # The rules
221
+ # --------------------------------------------------------------------------
222
+
223
+
224
+ def _decision(code, decision, reason=None, message="", skip=None, handoff=None):
225
+ return {"code": code, "decision": decision, "reason": reason, "skip": skip, "message": message,
226
+ "handoff": handoff}
227
+
228
+
229
+ def _halt(code, reason, message, handoff=None):
230
+ return _decision(code, "halt", reason, message, handoff=handoff)
231
+
232
+
233
+ def _go(code, message, handoff=None):
234
+ return _decision(code, "continue", message=message, handoff=handoff)
235
+
236
+
237
+ def _off_the_rule(code, what, raw, allowed):
238
+ """Halt on a missing value, or one outside the rule's set."""
239
+ if raw is None:
240
+ return _halt(code, f"no-{what}", f"{code} reported no {what}.")
241
+ return _halt(code, f"unknown-{what}", f"{code} reported the {what} {raw!r}, which is not {allowed}.")
242
+
243
+
244
+ def _gate_ts(result: dict) -> dict:
245
+ raw = _field(result, "verdict", "result")
246
+ verdict = TS_VERDICTS.get(_token(raw) or "")
247
+ if verdict is None:
248
+ if raw is None and _is_error(result):
249
+ reason = _halt_reason(result)
250
+ return _halt("TS", reason, f"TS halted before a verdict ({reason}).")
251
+ return _off_the_rule("TS", "verdict", raw, "PASS, FAIL, INCONCLUSIVE or pass-with-drift")
252
+ if verdict != "PASS":
253
+ detail = f" ({_halt_reason(result)})" if _is_error(result) else ""
254
+ return _halt("TS", verdict, f"TS verdict {verdict}{detail}: the skill is not exported.")
255
+ if _is_error(result):
256
+ reason = _halt_reason(result)
257
+ return _halt("TS", reason, f"TS reported PASS but ended in an error ({reason}): the skill is not exported.")
258
+ route = result.get("next_workflow", _MISSING)
259
+ if route not in (_MISSING, EXPORT_ROUTE):
260
+ message = f"TS verdict PASS, but next_workflow is {route!r}, not {EXPORT_ROUTE!r}."
261
+ return _halt("TS", "not-routed-to-export", message)
262
+ return _go("TS", "TS verdict PASS: the skill goes on to export.")
263
+
264
+
265
+ def _gate_an(result: dict, minimum: int) -> dict:
266
+ if _is_error(result):
267
+ reason = _halt_reason(result)
268
+ return _halt("AN", reason, f"AN halted ({reason}).")
269
+ status = str(result.get("status", "")).strip().lower()
270
+ if status == "redirect":
271
+ name = result.get("skill_name") or "the existing skill"
272
+ return _halt("AN", "redirect", f"AN routed the target to an update of {name}: run US {name}.")
273
+ if status == "skipped":
274
+ why = result.get("skipped_reason") or "AN skipped the target"
275
+ return _halt("AN", "skipped", f"{why}: there is no brief to go on with.")
276
+ units = _units(result)
277
+ if units is None:
278
+ return _halt("AN", "units-unknown", "AN reported no unit count and no brief paths.")
279
+ if units < minimum:
280
+ reason = "no-skillable-units" if units == 0 else "units-below-min"
281
+ message = f"AN confirmed {units} skillable unit(s); the pipeline needs at least {minimum}."
282
+ return _halt("AN", reason, message)
283
+ return _go("AN", f"AN confirmed {units} skillable unit(s).")
284
+
285
+
286
+ def _gate_as(result: dict, next_code: str | None) -> dict:
287
+ if _is_error(result):
288
+ reason = _halt_reason(result)
289
+ return _halt("AS", reason, f"AS halted ({reason}).")
290
+ raw = _field(result, "drift_score", "severity")
291
+ severity = _token(raw)
292
+ if severity not in AS_SEVERITIES:
293
+ return _off_the_rule("AS", "severity", raw, "CLEAN, MINOR, SIGNIFICANT or CRITICAL")
294
+ moved = _field(result, "upstream_moved", "upstream_moved") is True
295
+ ref = _field(result, "upstream_ref", "upstream_ref")
296
+ handoff = {"target_ref": ref.strip()} if moved and isinstance(ref, str) and ref.strip() else None
297
+ if severity == "CRITICAL":
298
+ message = "AS found CRITICAL drift: review the drift report, then run US on the skill."
299
+ return _halt("AS", "CRITICAL", message, handoff)
300
+ if severity == "CLEAN" and next_code == "US":
301
+ if _field(result, "next_workflow", "next_workflow") == UPDATE_ROUTE:
302
+ where = f" to {ref.strip()}" if isinstance(ref, str) and ref.strip() else ""
303
+ return _go("AS", f"AS found no drift, but upstream moved{where}: updating.", handoff)
304
+ return _decision("AS", "skip", "CLEAN", "No drift detected: skipping update.", skip="US")
305
+ return _go("AS", f"AS drift severity {severity}.", handoff)
306
+
307
+
308
+ def _gate_vs(result: dict) -> dict:
309
+ if _is_error(result):
310
+ reason = _halt_reason(result)
311
+ return _halt("VS", reason, f"VS halted ({reason}).")
312
+ raw = _field(result, "overall_verdict", "overallVerdict")
313
+ verdict = _token(raw)
314
+ if verdict not in VS_VERDICTS:
315
+ return _off_the_rule("VS", "verdict", raw, "FEASIBLE, CONDITIONALLY_FEASIBLE or NOT_FEASIBLE")
316
+ raw_coverage = _field(result, "coverage_percentage", "coveragePercentage")
317
+ coverage = _percentage(raw_coverage)
318
+ if coverage is None:
319
+ return _off_the_rule("VS", "coverage", raw_coverage, "a percentage from 0 to 100")
320
+ if coverage == 0:
321
+ message = ("VS covered none of the architecture's technologies (coverage 0%), so it "
322
+ "verified nothing to go on with: create their skills, then re-run VS.")
323
+ return _halt("VS", "zero-coverage", message)
324
+ return _go("VS", f"VS verdict {verdict} at {coverage}% coverage.")
325
+
326
+
327
+ _UNREADABLE = {
328
+ "no-result": "{code} left no result envelope to read.",
329
+ "result-unreadable": "The result given for {code} is not one JSON object, or it cannot be read as UTF-8.",
330
+ "wrong-result": "The result given for {code} is another workflow's envelope.",
331
+ }
332
+
333
+
334
+ def gate(code: str, text: str | None, *, next_code: str | None = None, minimum: int = 1) -> dict:
335
+ """Decide continue, skip or halt for `code` from the text its step produced."""
336
+ code = code.upper()
337
+ result, problem = read_result(code, text)
338
+ if problem:
339
+ return _halt(code, problem, _UNREADABLE[problem].format(code=code))
340
+ if code == "TS":
341
+ return _gate_ts(result)
342
+ if code == "AN":
343
+ return _gate_an(result, minimum)
344
+ if code == "AS":
345
+ return _gate_as(result, next_code.upper() if next_code else None)
346
+ return _gate_vs(result)
347
+
348
+
349
+ # --------------------------------------------------------------------------
350
+ # CLI
351
+ # --------------------------------------------------------------------------
352
+
353
+
354
+ def _build_parser() -> argparse.ArgumentParser:
355
+ parser = argparse.ArgumentParser(
356
+ prog="pipeline-gate",
357
+ description=(
358
+ "Circuit-breaker decision for one finished pipeline step "
359
+ "(pipeline-mode.md step 4d): read the step's result envelope line "
360
+ "or result record and print continue, skip or halt with a reason."
361
+ ),
362
+ formatter_class=argparse.RawDescriptionHelpFormatter,
363
+ )
364
+ parser.add_argument(
365
+ "--code",
366
+ required=True,
367
+ type=str.upper,
368
+ choices=GATED_CODES,
369
+ help="The code of the step that finished.",
370
+ )
371
+ parser.add_argument(
372
+ "--next",
373
+ type=str.upper,
374
+ help="The code of the next workflow in the plan, if any (a CLEAN AS skips a next US, unless it "
375
+ "routes to update-skill).",
376
+ )
377
+ parser.add_argument(
378
+ "--min",
379
+ type=int,
380
+ default=1,
381
+ help="The fewest units AN must confirm (AN's [min:N]; default 1). Read for AN only.",
382
+ )
383
+ parser.add_argument(
384
+ "--result",
385
+ help=(
386
+ "A file holding the envelope line or a result record, for tests and CI "
387
+ "(stdin when omitted or '-'). Pipeline Mode gates the envelope line the "
388
+ "step just printed: a -latest.json record may be an earlier run's."
389
+ ),
390
+ )
391
+ return parser
392
+
393
+
394
+ def _force_utf8(stream) -> None:
395
+ """Reconfigure the JSON output to UTF-8 (a Windows console uses cp1252)."""
396
+ if hasattr(stream, "reconfigure"):
397
+ errors = getattr(stream, "errors", None)
398
+ if errors is None:
399
+ stream.reconfigure(encoding="utf-8")
400
+ else:
401
+ stream.reconfigure(encoding="utf-8", errors=errors)
402
+
403
+
404
+ def _read_input(path: str | None) -> str | None:
405
+ """The text to gate, or None when it cannot be read as UTF-8.
406
+
407
+ A byte-order mark is dropped: a Windows editor or shell may write one.
408
+ """
409
+ try:
410
+ if path in (None, "-"):
411
+ if hasattr(sys.stdin, "reconfigure"):
412
+ sys.stdin.reconfigure(encoding="utf-8-sig", errors="strict")
413
+ return sys.stdin.read()
414
+ return Path(path).read_text(encoding="utf-8-sig")
415
+ except (OSError, UnicodeDecodeError):
416
+ return None
417
+
418
+
419
+ def main(argv: list[str] | None = None) -> int:
420
+ _force_utf8(sys.stdout)
421
+ args = _build_parser().parse_args(argv)
422
+ decision = gate(args.code, _read_input(args.result), next_code=args.next, minimum=args.min)
423
+ print(json.dumps(decision, indent=2))
424
+ return 0
425
+
426
+
427
+ if __name__ == "__main__":
428
+ sys.exit(main())