bmad-module-skill-forge 2.2.0 → 4.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 (437) 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 +143 -18
  18. package/docs/verifying-a-skill.md +87 -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 +3 -3
  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 +31 -59
  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 +233 -64
  64. package/src/shared/scripts/skf-build-change-manifest.py +2753 -28
  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 +772 -0
  69. package/src/shared/scripts/skf-comention-pairs.py +1333 -123
  70. package/src/shared/scripts/skf-compare-file-hashes.py +267 -105
  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 +21 -3
  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 +197 -55
  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 +5247 -96
  86. package/src/shared/scripts/skf-extraction-inventory.py +1280 -0
  87. package/src/shared/scripts/skf-extraction-snapshot.py +664 -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 +469 -205
  92. package/src/shared/scripts/skf-github-fetch.py +365 -0
  93. package/src/shared/scripts/skf-github-probe.py +700 -0
  94. package/src/shared/scripts/skf-hash-content.py +180 -20
  95. package/src/shared/scripts/skf-language-corpora.py +1 -1
  96. package/src/shared/scripts/skf-load-provenance.py +161 -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 +143 -20
  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 +176 -28
  105. package/src/shared/scripts/skf-promote-staged.py +430 -0
  106. package/src/shared/scripts/skf-provenance-gap-dispatch.py +64 -8
  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 +155 -14
  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 +787 -121
  127. package/src/shared/scripts/skf-tessl-review.py +706 -0
  128. package/src/shared/scripts/skf-update-active-symlink.py +120 -67
  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 +23 -2
  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 +3945 -152
  138. package/src/shared/scripts/skf-write-skill-brief.py +406 -25
  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 +28 -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/health-check.md +2 -0
  168. package/src/skf-audit-skill/references/init.md +149 -210
  169. package/src/skf-audit-skill/references/re-index.md +103 -110
  170. package/src/skf-audit-skill/references/report.md +96 -33
  171. package/src/skf-audit-skill/references/semantic-diff.md +34 -25
  172. package/src/skf-audit-skill/references/severity-classify.md +79 -65
  173. package/src/skf-audit-skill/references/severity-rules.md +15 -6
  174. package/src/skf-audit-skill/references/structural-diff.md +80 -90
  175. package/src/skf-audit-skill/references/upstream-checkout.md +77 -0
  176. package/src/skf-audit-skill/scripts/render-drift-tables.py +712 -0
  177. package/src/skf-brief-skill/SKILL.md +37 -26
  178. package/src/skf-brief-skill/assets/description-voice-examples.md +1 -1
  179. package/src/skf-brief-skill/assets/scope-templates.md +18 -25
  180. package/src/skf-brief-skill/assets/skill-brief-schema.md +30 -23
  181. package/src/skf-brief-skill/customize.toml +48 -20
  182. package/src/skf-brief-skill/references/analyze-target.md +144 -127
  183. package/src/skf-brief-skill/references/confirm-brief.md +11 -9
  184. package/src/skf-brief-skill/references/draft-checkpoint.md +36 -17
  185. package/src/skf-brief-skill/references/gather-intent-ratify.md +68 -0
  186. package/src/skf-brief-skill/references/gather-intent.md +87 -182
  187. package/src/skf-brief-skill/references/headless-args.md +84 -16
  188. package/src/skf-brief-skill/references/health-check.md +1 -1
  189. package/src/skf-brief-skill/references/invocation-contract.md +18 -13
  190. package/src/skf-brief-skill/references/portfolio-similarity-check.md +1 -1
  191. package/src/skf-brief-skill/references/qmd-collection-registration.md +7 -10
  192. package/src/skf-brief-skill/references/scope-definition.md +123 -47
  193. package/src/skf-brief-skill/references/step-auto-brief.md +26 -100
  194. package/src/skf-brief-skill/references/step-auto-validate.md +20 -121
  195. package/src/skf-brief-skill/references/version-resolution.md +7 -10
  196. package/src/skf-brief-skill/references/write-brief.md +66 -57
  197. package/src/skf-campaign/SKILL.md +31 -34
  198. package/src/skf-campaign/assets/campaign-state-schema.json +25 -4
  199. package/src/skf-campaign/customize.toml +47 -16
  200. package/src/skf-campaign/references/campaign-contracts.md +39 -29
  201. package/src/skf-campaign/references/campaign-directive-spec.md +4 -2
  202. package/src/skf-campaign/references/health-check.md +7 -16
  203. package/src/skf-campaign/references/step-01-setup.md +46 -84
  204. package/src/skf-campaign/references/step-02-strategy.md +31 -30
  205. package/src/skf-campaign/references/step-03-pins.md +26 -18
  206. package/src/skf-campaign/references/step-04-provenance.md +24 -23
  207. package/src/skf-campaign/references/step-05-skill-loop.md +67 -48
  208. package/src/skf-campaign/references/step-06-batch.md +41 -34
  209. package/src/skf-campaign/references/step-07-capstone.md +40 -25
  210. package/src/skf-campaign/references/step-08-verify.md +28 -29
  211. package/src/skf-campaign/references/step-09-refine.md +29 -30
  212. package/src/skf-campaign/references/step-10-export.md +48 -36
  213. package/src/skf-campaign/references/step-11-maintenance.md +31 -34
  214. package/src/skf-campaign/references/step-resume.md +37 -68
  215. package/src/skf-campaign/scripts/campaign-deps.py +57 -18
  216. package/src/skf-campaign/scripts/campaign-parse-manifest.py +361 -54
  217. package/src/skf-campaign/scripts/campaign-provenance.py +96 -53
  218. package/src/skf-campaign/scripts/campaign-quality-gate.py +592 -0
  219. package/src/skf-campaign/scripts/campaign-render-batch.py +257 -49
  220. package/src/skf-campaign/scripts/campaign-render-kickoff.py +343 -50
  221. package/src/skf-campaign/scripts/campaign-report.py +204 -37
  222. package/src/skf-campaign/scripts/campaign-state.py +1128 -0
  223. package/src/skf-campaign/scripts/campaign-status.py +10 -74
  224. package/src/skf-campaign/scripts/campaign-validate-pins.py +30 -3
  225. package/src/skf-campaign/scripts/campaign-validate-state.py +34 -4
  226. package/src/skf-campaign/templates/campaign-brief-template.yaml +4 -5
  227. package/src/skf-campaign/templates/campaign-report-template.md +5 -1
  228. package/src/skf-campaign/templates/kickoff-template.md +0 -14
  229. package/src/skf-create-skill/SKILL.md +27 -20
  230. package/src/skf-create-skill/assets/compile-assembly-rules.md +11 -8
  231. package/src/skf-create-skill/assets/skill-sections.md +21 -11
  232. package/src/skf-create-skill/customize.toml +20 -8
  233. package/src/skf-create-skill/references/authoritative-files-protocol.md +36 -58
  234. package/src/skf-create-skill/references/batch-mode.md +81 -0
  235. package/src/skf-create-skill/references/ccc-index-check.md +27 -0
  236. package/src/skf-create-skill/references/compile.md +119 -70
  237. package/src/skf-create-skill/references/component-extraction.md +113 -91
  238. package/src/skf-create-skill/references/enrich.md +41 -23
  239. package/src/skf-create-skill/references/entry-points-by-hand.md +37 -0
  240. package/src/skf-create-skill/references/extract.md +204 -162
  241. package/src/skf-create-skill/references/extraction-patterns-by-hand.md +141 -0
  242. package/src/skf-create-skill/references/extraction-patterns-tracing.md +11 -11
  243. package/src/skf-create-skill/references/extraction-patterns.md +588 -201
  244. package/src/skf-create-skill/references/generate-artifacts.md +145 -93
  245. package/src/skf-create-skill/references/health-check.md +1 -1
  246. package/src/skf-create-skill/references/load-brief.md +53 -36
  247. package/src/skf-create-skill/references/relabel-rule.md +12 -0
  248. package/src/skf-create-skill/references/report.md +37 -57
  249. package/src/skf-create-skill/references/source-resolution-protocols.md +58 -169
  250. package/src/skf-create-skill/references/step-auto-shard.md +20 -93
  251. package/src/skf-create-skill/references/step-doc-rot.md +44 -60
  252. package/src/skf-create-skill/references/step-doc-sources.md +28 -19
  253. package/src/skf-create-skill/references/sub/ccc-discover.md +18 -7
  254. package/src/skf-create-skill/references/sub/fetch-docs.md +106 -59
  255. package/src/skf-create-skill/references/sub/fetch-temporal.md +53 -166
  256. package/src/skf-create-skill/references/tier-degradation-rules.md +18 -9
  257. package/src/skf-create-skill/references/validate.md +173 -99
  258. package/src/skf-create-skill/scripts/scan-doc-rot.py +232 -99
  259. package/src/skf-create-stack-skill/SKILL.md +29 -56
  260. package/src/skf-create-stack-skill/assets/metadata-contract.md +51 -0
  261. package/src/skf-create-stack-skill/assets/provenance-map-schema.md +15 -15
  262. package/src/skf-create-stack-skill/assets/stack-skill-template.md +23 -57
  263. package/src/skf-create-stack-skill/customize.toml +23 -12
  264. package/src/skf-create-stack-skill/references/compile-stack.md +57 -66
  265. package/src/skf-create-stack-skill/references/compose-mode-rules.md +24 -44
  266. package/src/skf-create-stack-skill/references/detect-integrations.md +92 -95
  267. package/src/skf-create-stack-skill/references/detect-manifests.md +46 -71
  268. package/src/skf-create-stack-skill/references/generate-output.md +138 -128
  269. package/src/skf-create-stack-skill/references/init.md +90 -32
  270. package/src/skf-create-stack-skill/references/integration-patterns.md +1 -17
  271. package/src/skf-create-stack-skill/references/invocation-contract.md +34 -0
  272. package/src/skf-create-stack-skill/references/manifest-patterns.md +32 -20
  273. package/src/skf-create-stack-skill/references/parallel-extract.md +112 -38
  274. package/src/skf-create-stack-skill/references/rank-and-confirm.md +57 -75
  275. package/src/skf-create-stack-skill/references/report.md +39 -33
  276. package/src/skf-create-stack-skill/references/validate.md +20 -39
  277. package/src/skf-drop-skill/SKILL.md +28 -51
  278. package/src/skf-drop-skill/customize.toml +44 -30
  279. package/src/skf-drop-skill/references/execute.md +83 -146
  280. package/src/skf-drop-skill/references/health-check.md +2 -0
  281. package/src/skf-drop-skill/references/invocation-contract.md +64 -0
  282. package/src/skf-drop-skill/references/report.md +46 -30
  283. package/src/skf-drop-skill/references/select.md +122 -124
  284. package/src/skf-drop-skill/scripts/dir-sizes.py +54 -26
  285. package/src/skf-drop-skill/scripts/drop-roster.py +322 -0
  286. package/src/skf-export-skill/SKILL.md +40 -62
  287. package/src/skf-export-skill/assets/managed-section-format.md +14 -50
  288. package/src/skf-export-skill/assets/snippet-format.md +3 -1
  289. package/src/skf-export-skill/customize.toml +42 -13
  290. package/src/skf-export-skill/references/generate-snippet.md +54 -46
  291. package/src/skf-export-skill/references/health-check.md +2 -0
  292. package/src/skf-export-skill/references/invocation-contract.md +15 -0
  293. package/src/skf-export-skill/references/load-skill.md +88 -62
  294. package/src/skf-export-skill/references/manifest-rebuild.md +9 -10
  295. package/src/skf-export-skill/references/multi-skill-mode.md +4 -4
  296. package/src/skf-export-skill/references/orphan-context-detection.md +13 -5
  297. package/src/skf-export-skill/references/orphan-row-detection.md +31 -22
  298. package/src/skf-export-skill/references/package.md +15 -73
  299. package/src/skf-export-skill/references/preflight-snippet-root-probe.md +51 -70
  300. package/src/skf-export-skill/references/result-envelope.md +31 -16
  301. package/src/skf-export-skill/references/summary.md +41 -17
  302. package/src/skf-export-skill/references/token-report.md +4 -12
  303. package/src/skf-export-skill/references/update-context.md +118 -237
  304. package/src/skf-forger/SKILL.md +61 -37
  305. package/src/skf-forger/bmad-skill-manifest.yaml +4 -1
  306. package/src/skf-forger/customize.toml +43 -0
  307. package/src/skf-forger/references/pipeline-mode.md +73 -18
  308. package/src/skf-forger/scripts/forge-status.py +299 -0
  309. package/src/skf-forger/scripts/parse-pipeline.py +322 -54
  310. package/src/skf-forger/scripts/pipeline-gate.py +428 -0
  311. package/src/skf-forger/scripts/pipeline-journal.py +1052 -0
  312. package/src/skf-quick-skill/SKILL.md +54 -53
  313. package/src/skf-quick-skill/assets/skill-template.md +4 -2
  314. package/src/skf-quick-skill/customize.toml +51 -20
  315. package/src/skf-quick-skill/references/batch-mode.md +74 -21
  316. package/src/skf-quick-skill/references/compile.md +30 -31
  317. package/src/skf-quick-skill/references/ecosystem-check.md +7 -57
  318. package/src/skf-quick-skill/references/finalize.md +69 -18
  319. package/src/skf-quick-skill/references/halt-contract.md +41 -29
  320. package/src/skf-quick-skill/references/health-check.md +5 -3
  321. package/src/skf-quick-skill/references/quick-extract.md +114 -45
  322. package/src/skf-quick-skill/references/registry-resolution.md +9 -64
  323. package/src/skf-quick-skill/references/resolve-target.md +116 -66
  324. package/src/skf-quick-skill/references/write-and-validate.md +79 -40
  325. package/src/skf-refine-architecture/SKILL.md +43 -53
  326. package/src/skf-refine-architecture/customize.toml +49 -20
  327. package/src/skf-refine-architecture/references/compile.md +97 -54
  328. package/src/skf-refine-architecture/references/exit-codes.md +30 -0
  329. package/src/skf-refine-architecture/references/finding-storage.md +21 -0
  330. package/src/skf-refine-architecture/references/gap-analysis.md +101 -38
  331. package/src/skf-refine-architecture/references/improvements.md +14 -20
  332. package/src/skf-refine-architecture/references/init.md +98 -57
  333. package/src/skf-refine-architecture/references/issue-detection.md +32 -39
  334. package/src/skf-refine-architecture/references/refinement-rules.md +37 -48
  335. package/src/skf-refine-architecture/references/report.md +65 -54
  336. package/src/skf-refine-architecture/scripts/skf-check-preservation.py +1619 -0
  337. package/src/skf-rename-skill/SKILL.md +19 -54
  338. package/src/skf-rename-skill/customize.toml +32 -26
  339. package/src/skf-rename-skill/references/execute.md +230 -184
  340. package/src/skf-rename-skill/references/exit-codes.md +7 -7
  341. package/src/skf-rename-skill/references/health-check.md +1 -7
  342. package/src/skf-rename-skill/references/invocation-contract.md +25 -0
  343. package/src/skf-rename-skill/references/report.md +63 -52
  344. package/src/skf-rename-skill/references/select.md +197 -115
  345. package/src/skf-rename-skill/scripts/skf-validate-rename-name.py +161 -21
  346. package/src/skf-setup/SKILL.md +26 -26
  347. package/src/skf-setup/customize.toml +14 -10
  348. package/src/skf-setup/references/auto-index.md +50 -63
  349. package/src/skf-setup/references/ccc-index.md +29 -90
  350. package/src/skf-setup/references/detect-and-tier.md +58 -67
  351. package/src/skf-setup/references/health-check.md +2 -0
  352. package/src/skf-setup/references/invocation-contract.md +14 -0
  353. package/src/skf-setup/references/report.md +43 -166
  354. package/src/skf-setup/references/tier-rules.md +5 -5
  355. package/src/skf-setup/references/write-config.md +34 -57
  356. package/src/skf-test-skill/SKILL.md +17 -57
  357. package/src/skf-test-skill/assets/output-section-formats.md +33 -34
  358. package/src/skf-test-skill/customize.toml +27 -13
  359. package/src/skf-test-skill/references/coherence-check.md +134 -62
  360. package/src/skf-test-skill/references/coverage-check-tiers.md +35 -0
  361. package/src/skf-test-skill/references/coverage-check.md +209 -264
  362. package/src/skf-test-skill/references/detect-mode.md +5 -5
  363. package/src/skf-test-skill/references/external-validators.md +94 -82
  364. package/src/skf-test-skill/references/health-check.md +6 -7
  365. package/src/skf-test-skill/references/init.md +164 -91
  366. package/src/skf-test-skill/references/invocation-contract.md +66 -0
  367. package/src/skf-test-skill/references/migration-section-rules.md +5 -2
  368. package/src/skf-test-skill/references/report.md +164 -142
  369. package/src/skf-test-skill/references/score.md +80 -107
  370. package/src/skf-test-skill/references/scoring-rules.md +42 -155
  371. package/src/skf-test-skill/references/source-access-protocol.md +18 -94
  372. package/src/skf-test-skill/references/step-hard-gate.md +51 -33
  373. package/src/skf-test-skill/scripts/aggregate-coherence.py +218 -30
  374. package/src/skf-test-skill/scripts/build-result-context.py +380 -0
  375. package/src/skf-test-skill/scripts/check-metadata-coherence.py +37 -4
  376. package/src/skf-test-skill/scripts/combine-external-scores.py +23 -6
  377. package/src/skf-test-skill/scripts/compute-score.py +223 -45
  378. package/src/skf-test-skill/scripts/gap-ledger.py +1666 -0
  379. package/src/skf-test-skill/scripts/hard-gate.py +203 -0
  380. package/src/skf-test-skill/scripts/load-coverage-inputs.py +1198 -0
  381. package/src/skf-test-skill/scripts/locate-export-segments.py +835 -0
  382. package/src/skf-test-skill/scripts/reconcile-coverage.py +547 -79
  383. package/src/skf-test-skill/scripts/score-signatures.py +479 -0
  384. package/src/skf-test-skill/scripts/stage-helper-payload.py +148 -0
  385. package/src/skf-test-skill/scripts/validate-inventory.py +311 -16
  386. package/src/skf-test-skill/scripts/verify-declared-numerator.py +133 -56
  387. package/src/skf-test-skill/templates/test-report-template.md +21 -15
  388. package/src/skf-update-skill/SKILL.md +38 -25
  389. package/src/skf-update-skill/customize.toml +27 -12
  390. package/src/skf-update-skill/references/detect-changes.md +253 -223
  391. package/src/skf-update-skill/references/gap-driven.md +336 -0
  392. package/src/skf-update-skill/references/health-check.md +31 -5
  393. package/src/skf-update-skill/references/init.md +254 -84
  394. package/src/skf-update-skill/references/invocation-contract.md +12 -0
  395. package/src/skf-update-skill/references/manual-section-rules.md +6 -23
  396. package/src/skf-update-skill/references/merge.md +182 -49
  397. package/src/skf-update-skill/references/re-extract.md +53 -262
  398. package/src/skf-update-skill/references/report.md +102 -35
  399. package/src/skf-update-skill/references/write.md +357 -155
  400. package/src/skf-update-skill/scripts/skf-new-file-diff.py +183 -46
  401. package/src/skf-verify-stack/SKILL.md +23 -42
  402. package/src/skf-verify-stack/assets/feasibility-report-template.md +11 -5
  403. package/src/skf-verify-stack/customize.toml +55 -22
  404. package/src/skf-verify-stack/references/coverage-patterns.md +17 -8
  405. package/src/skf-verify-stack/references/coverage.md +67 -64
  406. package/src/skf-verify-stack/references/exit-codes.md +21 -8
  407. package/src/skf-verify-stack/references/init.md +107 -62
  408. package/src/skf-verify-stack/references/integration-verification-rules.md +12 -13
  409. package/src/skf-verify-stack/references/integrations.md +106 -65
  410. package/src/skf-verify-stack/references/report.md +59 -42
  411. package/src/skf-verify-stack/references/requirements.md +32 -9
  412. package/src/skf-verify-stack/references/synthesize.md +53 -39
  413. package/src/skf-verify-stack/scripts/skf-coverage-tally.py +398 -52
  414. package/src/skf-verify-stack/scripts/skf-previous-report.py +330 -0
  415. package/src/skf-verify-stack/scripts/skf-report-delta.py +453 -49
  416. package/src/skf-verify-stack/scripts/skf-verdict-rollup.py +240 -27
  417. package/tools/cli/commands/install.js +11 -4
  418. package/tools/cli/commands/status.js +15 -3
  419. package/tools/cli/commands/update.js +11 -4
  420. package/tools/cli/lib/tool-check.js +320 -0
  421. package/tools/cli/lib/version-check.js +22 -9
  422. package/tools/cli/skf-cli.js +4 -1
  423. package/.gitattributes +0 -16
  424. package/.nvmrc +0 -1
  425. package/docs/_internal/RELEASING.md +0 -679
  426. package/release-audits/v1.0.0-launch-audit.md +0 -728
  427. package/src/skf-audit-skill/references/step-doc-drift.md +0 -177
  428. package/src/skf-brief-skill/references/headless-source-authority-detection.md +0 -26
  429. package/src/skf-campaign/manifest.yaml +0 -15
  430. package/src/skf-create-skill/assets/tessl-dismissal-rules.md +0 -106
  431. package/src/skf-create-skill/references/ecosystem-check.md +0 -74
  432. package/src/skf-drop-skill/references/headless-contract.md +0 -18
  433. package/src/skf-update-skill/references/merge-conflict-rules.md +0 -15
  434. package/src/skf-update-skill/references/remote-source-resolution.md +0 -98
  435. package/src/skf-update-skill/references/validate.md +0 -124
  436. package/tools/validate-docs-drift.js +0 -233
  437. package/tools/validate-docs-links.js +0 -665
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """SKF test-skill result context: the emitter payload of a finished run.
7
+
8
+ report.md §4c hands the shared emitter (`skf-emit-result-envelope.py emit
9
+ --workflow skf-test-skill`) one payload, which the emitter turns into the
10
+ SKF_TEST_RESULT_JSON line and the run's result files. Every value in it
11
+ follows from records the run already wrote, so this script builds it rather
12
+ than the model retyping them from memory:
13
+
14
+ - the report frontmatter: skillName, runId, testMode, hardGate,
15
+ testResult, workspaceDrift, score, threshold, thresholdFallback,
16
+ originalThreshold and evidenceReportPath (the steps write the verdict
17
+ inputs there, so a compacted context cannot change them);
18
+ - the gap ledger: the gap counts by severity (gap-ledger.py's summary);
19
+ - the scoring script's output, which score.md saves: the verdict, score
20
+ and threshold it settled, activeCategories and inconclusiveReasons. A
21
+ run the hard gate blocked was never scored and has none.
22
+
23
+ A scored run's verdict fields are checked against the scoring output
24
+ (`scored_fields`): testResult must be the verdict the output settled,
25
+ `effectiveResult` when the output has one, else `result`, in lower case,
26
+ with `pass` read as `pass-with-drift` when the frontmatter workspaceDrift
27
+ is `overridden`; score its `totalScore`; threshold the one the verdict was
28
+ settled against (compute-score.py's FALLBACK_FLOOR after a threshold
29
+ fallback, else its `threshold`); and thresholdFallback and
30
+ originalThreshold its override group's. A field copied from anything else
31
+ (`result` over a cap's FAIL, a bare pass under drift, the target threshold
32
+ of a run the fallback accepted) exits 1 and writes no payload, so
33
+ report.md halts before it publishes the report or writes a result file.
34
+
35
+ The derived fields live here, in one place:
36
+
37
+ testResult verdict summary.result exit_code next_workflow
38
+ pass PASS PASS 0 export-skill
39
+ pass-with-drift pass-with-drift PASS_WITH_DRIFT 4 update-skill
40
+ fail FAIL FAIL 2 update-skill
41
+ inconclusive INCONCLUSIVE INCONCLUSIVE 3 null
42
+
43
+ A run the hard gate blocked (frontmatter hardGate: 'blocked') takes the
44
+ hard gate's own payload (hard-gate.py `blocked_envelope`): status error,
45
+ halt_reason hard-gate-blocked, verdict FAIL, exit 2, score and threshold
46
+ null. threshold_fallback and original_threshold appear only when the
47
+ threshold fallback fired: absent, never false or null, otherwise.
48
+
49
+ Payload (written to --output, UTF-8 JSON):
50
+ {<the envelope fields: status, skill_name, verdict, score, threshold,
51
+ report_path, next_workflow, exit_code, halt_reason, and
52
+ threshold_fallback and original_threshold when the fallback fired>,
53
+ "warnings": [<each --warning, in order>] (only when one is given),
54
+ "result_contract": {"skill": "skf-test-skill", <the envelope fields>,
55
+ "outputs": [{"type": "report", "path": <report>}],
56
+ "summary": {"score", "threshold", "result", "testMode",
57
+ "activeCategories", "inconclusiveReasons" (INCONCLUSIVE),
58
+ "threshold_fallback", "original_threshold",
59
+ "evidence_report_path" (fallback only), "gapCounts",
60
+ "hardGate"},
61
+ "runId": <runId>, "healthCheckDispatched": <bool>}}
62
+
63
+ CLI:
64
+ uv run build-result-context.py --report <report.md> [--report-path <path>]
65
+ --ledger <ledger.json> [--score <scoring output>] [--no-health-check]
66
+ [--warning <text>]... --output <result-context.json>
67
+
68
+ --score is required unless the frontmatter says the hard gate blocked the
69
+ run. --report-path is the path the payload names the report by (default:
70
+ --report): report.md builds the payload from the report before it gives
71
+ the report its public name, and passes that name here.
72
+
73
+ --warning hands the emitter a warning the run kept for its result (such as
74
+ `customization_resolver_unavailable: <reason>`); the emitter adds the ones
75
+ the run recorded in its sink, and stamps all of them into the envelope and
76
+ the result files.
77
+
78
+ stdout (one object): {"status", "verdict", "exit_code", "next_workflow",
79
+ "halt_reason", "target", "output"}; `target` is the --target the emit call
80
+ takes: "stderr" for a blocked run, else "stdout".
81
+
82
+ Exit codes:
83
+ 0 the payload was written
84
+ 1 an input is missing or unreadable, the report's frontmatter holds no
85
+ settled verdict or a verdict field the scoring output does not settle,
86
+ or --output cannot be written ({"status": "error", "error"} on stdout)
87
+ 2 a usage error: no --score for a run the hard gate did not block (the
88
+ same JSON on stdout), or a flag argparse refuses (its usage on stderr)
89
+ """
90
+
91
+ from __future__ import annotations
92
+
93
+ import argparse
94
+ import importlib.util
95
+ import json
96
+ import re
97
+ import sys
98
+ from pathlib import Path
99
+
100
+ WORKFLOW = "skf-test-skill"
101
+ # testResult -> (verdict, summary.result, exit_code, next_workflow)
102
+ VERDICTS = {
103
+ "pass": ("PASS", "PASS", 0, "export-skill"),
104
+ "pass-with-drift": ("pass-with-drift", "PASS_WITH_DRIFT", 4, "update-skill"),
105
+ "fail": ("FAIL", "FAIL", 2, "update-skill"),
106
+ "inconclusive": ("INCONCLUSIVE", "INCONCLUSIVE", 3, None),
107
+ }
108
+ _KEY_RE = re.compile(r"^([A-Za-z_][\w-]*):\s*(.*?)\s*$")
109
+
110
+
111
+ class ContextError(Exception):
112
+ """An input the payload cannot be built from: exit 1."""
113
+
114
+
115
+ class UsageError(ContextError):
116
+ """A command the caller can correct and run again: exit 2."""
117
+
118
+
119
+ def _sibling(name: str, module: str):
120
+ """Import a script that sits beside this one (gap-ledger.py, hard-gate.py)."""
121
+ path = Path(__file__).resolve().parent / name
122
+ spec = importlib.util.spec_from_file_location(module, path)
123
+ if spec is None or spec.loader is None or not path.is_file():
124
+ raise ContextError(f"{name} not found beside {Path(__file__).name}")
125
+ loaded = importlib.util.module_from_spec(spec)
126
+ spec.loader.exec_module(loaded)
127
+ return loaded
128
+
129
+
130
+ def read_frontmatter(text: str) -> dict[str, str]:
131
+ """Top-level scalar keys of the leading `---` block, quotes removed.
132
+
133
+ >>> read_frontmatter("---\\ntestResult: 'pass'\\nscore: 91.5%\\nsteps: ['a']\\n---\\n# R")
134
+ {'testResult': 'pass', 'score': '91.5%', 'steps': "['a']"}
135
+ >>> read_frontmatter("# no frontmatter")
136
+ {}
137
+ """
138
+ lines = text.lstrip("").split("\n")
139
+ if not lines or lines[0].strip() != "---":
140
+ return {}
141
+ out: dict[str, str] = {}
142
+ for line in lines[1:]:
143
+ if line.strip() == "---":
144
+ return out
145
+ m = _KEY_RE.match(line.rstrip("\r"))
146
+ if not m:
147
+ continue
148
+ value = m.group(2)
149
+ if len(value) >= 2 and value[0] == value[-1] and value[0] in "'\"":
150
+ value = value[1:-1]
151
+ out[m.group(1)] = value
152
+ return {}
153
+
154
+
155
+ def percent(value: str | None):
156
+ """'87.5%' -> 87.5, '90%' or '90' -> 90, '' or None -> None.
157
+
158
+ >>> percent("87.5%"), percent("90"), percent(""), percent(None)
159
+ (87.5, 90, None, None)
160
+ """
161
+ text = (value or "").strip().rstrip("%").strip()
162
+ if not text:
163
+ return None
164
+ try:
165
+ number = float(text)
166
+ except ValueError:
167
+ raise ContextError(f"not a percentage: {value!r}") from None
168
+ return int(number) if number.is_integer() else number
169
+
170
+
171
+ def _read_json(path: str, label: str):
172
+ try:
173
+ return json.loads(Path(path).read_bytes().decode("utf-8-sig"))
174
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
175
+ raise ContextError(f"{label} {path} cannot be read: {exc}") from exc
176
+
177
+
178
+ def _fallback_floor() -> int:
179
+ """The threshold a fallback accepts a run at: compute-score.py's, which applies the fallback."""
180
+ return _sibling("compute-score.py", "skf_compute_score_floor").FALLBACK_FLOOR
181
+
182
+
183
+ def settled_test_result(scored: dict, workspace_drift: str | None) -> str | None:
184
+ """The testResult the scoring output settles, or None when it holds no verdict.
185
+
186
+ >>> settled_test_result({"result": "PASS"}, "ok")
187
+ 'pass'
188
+ >>> settled_test_result({"result": "PASS", "effectiveResult": "FAIL", "capReason": "Cap 1"}, "ok")
189
+ 'fail'
190
+ >>> settled_test_result({"result": "FAIL", "effectiveResult": "PASS"}, "overridden")
191
+ 'pass-with-drift'
192
+ >>> settled_test_result({"result": "INCONCLUSIVE"}, "overridden")
193
+ 'inconclusive'
194
+ >>> settled_test_result({"error": "bad flag", "code": "INVALID_INPUT"}, "ok") is None
195
+ True
196
+ """
197
+ verdict = scored.get("effectiveResult") or scored.get("result")
198
+ settled = verdict.lower() if isinstance(verdict, str) else None
199
+ if settled not in ("pass", "fail", "inconclusive"):
200
+ return None
201
+ return "pass-with-drift" if settled == "pass" and workspace_drift == "overridden" else settled
202
+
203
+
204
+ def scored_fields(scored: dict, workspace_drift: str | None) -> dict | None:
205
+ """The frontmatter verdict fields the scoring output settles, or None when it holds no verdict.
206
+
207
+ >>> scored_fields({"result": "PASS", "totalScore": 91.5, "threshold": 80}, "ok")
208
+ {'testResult': 'pass', 'score': 91.5, 'threshold': 80, 'thresholdFallback': False, 'originalThreshold': None}
209
+ >>> fell_back = {"result": "FAIL", "effectiveResult": "PASS", "capReason": None, "thresholdFallback": True,
210
+ ... "originalThreshold": 90, "totalScore": 85, "threshold": 90}
211
+ >>> scored_fields(fell_back, "ok")
212
+ {'testResult': 'pass', 'score': 85, 'threshold': 80, 'thresholdFallback': True, 'originalThreshold': 90}
213
+ """
214
+ test_result = settled_test_result(scored, workspace_drift)
215
+ if test_result is None:
216
+ return None
217
+ fallback = scored.get("thresholdFallback") is True
218
+ return {
219
+ "testResult": test_result,
220
+ "score": scored.get("totalScore"),
221
+ "threshold": _fallback_floor() if fallback else scored.get("threshold"),
222
+ "thresholdFallback": fallback,
223
+ "originalThreshold": scored.get("originalThreshold") if fallback else None,
224
+ }
225
+
226
+
227
+ def build(report: str, ledger: str, score: str | None = None, health_check: bool = True,
228
+ warnings: list[str] | None = None, report_path: str | None = None) -> dict:
229
+ """The emitter payload for the run whose report and ledger are given; `report_path`
230
+ (default `report`) is the path the payload names the report by."""
231
+ report_path = report_path or report
232
+ try:
233
+ front = read_frontmatter(Path(report).read_bytes().decode("utf-8-sig"))
234
+ except (OSError, UnicodeDecodeError) as exc:
235
+ raise ContextError(f"--report {report} cannot be read: {exc}") from exc
236
+ if not front:
237
+ raise ContextError(f"--report {report} has no frontmatter")
238
+ gl = _sibling("gap-ledger.py", "skf_gap_ledger")
239
+ try:
240
+ counts = gl.summarize(Path(ledger), gl.load_ledger(Path(ledger)))["counts"]
241
+ except gl.LedgerError as exc:
242
+ raise ContextError(f"--ledger {ledger}: {exc}") from exc
243
+
244
+ skill = front.get("skillName") or None
245
+ blocked = front.get("hardGate") == "blocked"
246
+ if blocked:
247
+ envelope = dict(_sibling("hard-gate.py", "skf_hard_gate").blocked_envelope(skill, report_path))
248
+ result, active, inconclusive = "FAIL", [], None
249
+ else:
250
+ test_result = front.get("testResult", "")
251
+ if test_result not in VERDICTS:
252
+ raise ContextError(f"--report {report} holds no settled verdict (testResult {test_result!r})")
253
+ verdict, result, exit_code, next_workflow = VERDICTS[test_result]
254
+ envelope = {
255
+ "status": "success",
256
+ "skill_name": skill,
257
+ "verdict": verdict,
258
+ "score": percent(front.get("score")),
259
+ "threshold": percent(front.get("threshold")),
260
+ "report_path": report_path,
261
+ "next_workflow": next_workflow,
262
+ "exit_code": exit_code,
263
+ "halt_reason": None,
264
+ }
265
+ if not score:
266
+ raise UsageError("--score is required for a run the hard gate did not block: its verdict "
267
+ "fields are checked against the scoring output")
268
+ scored = _read_json(score, "--score")
269
+ if not isinstance(scored, dict):
270
+ raise ContextError(f"--score {score} is not a JSON object")
271
+ settled = scored_fields(scored, front.get("workspaceDrift"))
272
+ if settled is None:
273
+ raise ContextError(f"--score {score} holds no verdict (no effectiveResult or result)")
274
+ fell_back = front.get("thresholdFallback", "").lower() == "true"
275
+ written = {"testResult": test_result, "score": envelope["score"], "threshold": envelope["threshold"],
276
+ "thresholdFallback": fell_back,
277
+ "originalThreshold": percent(front.get("originalThreshold")) if fell_back else None}
278
+ wrong = [f"{key} {written[key]!r} where it settles {settled[key]!r}"
279
+ for key in settled if written[key] != settled[key]]
280
+ if wrong:
281
+ raise ContextError(f"--report {report} disagrees with the scoring output {score}: "
282
+ + "; ".join(wrong))
283
+ active = scored.get("activeCategories", [])
284
+ inconclusive = scored.get("inconclusiveReasons") if verdict == "INCONCLUSIVE" else None
285
+
286
+ fallback = front.get("thresholdFallback", "").lower() == "true" and not blocked
287
+ if fallback:
288
+ envelope["threshold_fallback"] = True
289
+ envelope["original_threshold"] = percent(front.get("originalThreshold"))
290
+
291
+ summary = {
292
+ "score": envelope["score"],
293
+ "threshold": envelope["threshold"],
294
+ "result": result,
295
+ "testMode": front.get("testMode") or None,
296
+ "activeCategories": active,
297
+ }
298
+ if inconclusive is not None:
299
+ summary["inconclusiveReasons"] = inconclusive
300
+ if fallback:
301
+ summary["threshold_fallback"] = True
302
+ summary["original_threshold"] = envelope["original_threshold"]
303
+ summary["evidence_report_path"] = front.get("evidenceReportPath") or None
304
+ summary["gapCounts"] = counts
305
+ summary["hardGate"] = front.get("hardGate") or None
306
+
307
+ contract = {"skill": WORKFLOW, **envelope,
308
+ "outputs": [{"type": "report", "path": report_path}],
309
+ "summary": summary,
310
+ "runId": front.get("runId") or None,
311
+ "healthCheckDispatched": health_check}
312
+ payload = {**envelope, "result_contract": contract}
313
+ kept = [w.strip() for w in warnings or [] if w and w.strip()]
314
+ if kept:
315
+ payload["warnings"] = kept
316
+ return payload
317
+
318
+
319
+ def _build_parser() -> argparse.ArgumentParser:
320
+ parser = argparse.ArgumentParser(
321
+ prog="build-result-context",
322
+ description=("Build the shared emitter's payload for a finished test-skill run from its "
323
+ "report frontmatter, its gap ledger and the scoring output."),
324
+ )
325
+ parser.add_argument("--report", required=True, help="the test report to read")
326
+ parser.add_argument("--report-path",
327
+ help="the path the payload names the report by: its public name (default: --report)")
328
+ parser.add_argument("--ledger", required=True, help="the run's gap ledger")
329
+ parser.add_argument("--score", help="the scoring script's output (required unless the hard gate blocked "
330
+ "the run)")
331
+ parser.add_argument("--no-health-check", action="store_true",
332
+ help="the run was given --no-health-check (healthCheckDispatched false)")
333
+ parser.add_argument("--warning", action="append", default=[],
334
+ help="a warning the run kept for its result (repeatable)")
335
+ parser.add_argument("--output", required=True, help="where to write the payload")
336
+ return parser
337
+
338
+
339
+ def main(argv: list[str] | None = None) -> int:
340
+ args = _build_parser().parse_args(argv)
341
+ try:
342
+ payload = build(args.report, args.ledger, args.score, not args.no_health_check, args.warning,
343
+ args.report_path)
344
+ out = Path(args.output)
345
+ out.parent.mkdir(parents=True, exist_ok=True)
346
+ out.write_bytes((json.dumps(payload, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
347
+ except (ContextError, OSError) as exc:
348
+ print(json.dumps({"status": "error", "error": str(exc)}))
349
+ return 2 if isinstance(exc, UsageError) else 1
350
+ blocked = payload["halt_reason"] is not None
351
+ print(json.dumps({
352
+ "status": payload["status"],
353
+ "verdict": payload["verdict"],
354
+ "exit_code": payload["exit_code"],
355
+ "next_workflow": payload["next_workflow"],
356
+ "halt_reason": payload["halt_reason"],
357
+ "target": "stderr" if blocked else "stdout",
358
+ "output": out.as_posix(),
359
+ }, indent=2))
360
+ return 0
361
+
362
+
363
+ def _force_utf8(*streams) -> None:
364
+ """Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
365
+
366
+ A Windows console pipes them as cp1252, which cannot print every character
367
+ of the --help text, so --help would stop with UnicodeEncodeError.
368
+ """
369
+ for stream in streams:
370
+ if hasattr(stream, "reconfigure"):
371
+ errors = getattr(stream, "errors", None)
372
+ if errors is None:
373
+ stream.reconfigure(encoding="utf-8")
374
+ else:
375
+ stream.reconfigure(encoding="utf-8", errors=errors)
376
+
377
+
378
+ if __name__ == "__main__":
379
+ _force_utf8(sys.stdout, sys.stderr)
380
+ sys.exit(main())
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.11"
4
4
  # ///
5
5
  """Deterministic metadata export-count coherence cross-check.
6
6
 
@@ -39,10 +39,15 @@ Stack skills (`skill_type == "stack"`) and reference apps
39
39
  intentionally *different* surfaces, so comparing them yields only false drift.
40
40
 
41
41
  CLI usage:
42
+ uv run check-metadata-coherence.py --inputs <coverage-inputs.json> # a file
42
43
  uv run check-metadata-coherence.py '<JSON>' # positional
43
44
  uv run check-metadata-coherence.py --json-input '<JSON>' # explicit flag
44
45
  cat input.json | uv run check-metadata-coherence.py --stdin # piped
45
46
 
47
+ --inputs reads the input object from a file: load-coverage-inputs.py
48
+ metadata writes these keys from metadata.json and the provenance map (any
49
+ other key in the file is ignored), so no count is copied by hand.
50
+
46
51
  Input schema (one object; omit any count that is absent for the skill):
47
52
  {
48
53
  "skillType": "stack" | ... | null, # metadata.json.skill_type
@@ -83,11 +88,12 @@ from __future__ import annotations
83
88
  import argparse
84
89
  import json
85
90
  import sys
91
+ from pathlib import Path
86
92
 
87
93
  DEFAULT_DRIFT_PCT = 10
88
94
  CATEGORY = "structural/metadata coherence"
89
95
 
90
- # Stable source labels used in finding bodies (match coverage-check.md §4b examples).
96
+ # Stable source labels used in finding bodies.
91
97
  LABEL_EXPORTS_PUBLIC_API = "stats.exports_public_api"
92
98
  LABEL_EXPORTS_LENGTH = "exports[].length"
93
99
  LABEL_EXPORTS_DOCUMENTED = "stats.exports_documented"
@@ -317,12 +323,19 @@ def _build_parser():
317
323
  action="store_true",
318
324
  help="Read the JSON object from stdin.",
319
325
  )
326
+ src.add_argument(
327
+ "--inputs",
328
+ metavar="PATH",
329
+ help="Read the JSON object from this file (load-coverage-inputs.py metadata output).",
330
+ )
320
331
  return parser
321
332
 
322
333
 
323
334
  def _resolve_input(args):
324
335
  if args.stdin:
325
336
  return sys.stdin.read()
337
+ if args.inputs is not None:
338
+ return Path(args.inputs).read_bytes().decode("utf-8-sig")
326
339
  if args.json_input_flag is not None:
327
340
  return args.json_input_flag
328
341
  if args.json_input is not None:
@@ -333,11 +346,15 @@ def _resolve_input(args):
333
346
  def main(argv=None):
334
347
  parser = _build_parser()
335
348
  args = parser.parse_args(argv)
336
- raw = _resolve_input(args)
349
+ try:
350
+ raw = _resolve_input(args)
351
+ except (OSError, UnicodeDecodeError) as exc:
352
+ print(f"error: cannot read --inputs {args.inputs}: {exc}", file=sys.stderr)
353
+ return 1
337
354
  if not raw.strip():
338
355
  parser.print_usage(file=sys.stderr)
339
356
  print(
340
- "error: no input provided (positional arg, --json-input, or --stdin)",
357
+ "error: no input provided (positional arg, --json-input, --stdin or --inputs)",
341
358
  file=sys.stderr,
342
359
  )
343
360
  return 1
@@ -355,5 +372,21 @@ def main(argv=None):
355
372
  return 0
356
373
 
357
374
 
375
+ def _force_utf8(*streams) -> None:
376
+ """Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
377
+
378
+ A Windows console pipes them as cp1252, which cannot print every character
379
+ of the --help text, so --help would stop with UnicodeEncodeError.
380
+ """
381
+ for stream in streams:
382
+ if hasattr(stream, "reconfigure"):
383
+ errors = getattr(stream, "errors", None)
384
+ if errors is None:
385
+ stream.reconfigure(encoding="utf-8")
386
+ else:
387
+ stream.reconfigure(encoding="utf-8", errors=errors)
388
+
389
+
358
390
  if __name__ == "__main__":
391
+ _force_utf8(sys.stdout, sys.stderr)
359
392
  raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
  # /// script
3
- # requires-python = ">=3.10"
3
+ # requires-python = ">=3.11"
4
4
  # dependencies = []
5
5
  # ///
6
6
  """Deterministic combined external-validation score.
@@ -13,12 +13,12 @@ run-to-run (an odd-sum average such as (80 + 73) / 2 = 76.5 is exactly where an
13
13
  in-prompt round swings).
14
14
 
15
15
  Rule (external-validators.md §4):
16
- * both tools ran -> mean of skill-check + tessl review scores
16
+ * both tools ran -> mean of the skill-check and Tessl Review scores
17
17
  * one tool ran -> that tool's score
18
18
  * neither ran -> null (scoring step redistributes the external weight)
19
19
 
20
- Both scores are on the same 0-100 scale (skill-check quality score; tessl review
21
- percentage). Rounding matches compute-score.py (JS-compatible half-up) so the
20
+ Both scores are on the same 0-100 scale (skill-check's quality score; the Tessl
21
+ Review score, `review_score` from skf-tessl-review.py, when the user opted in). Rounding matches compute-score.py (JS-compatible half-up) so the
22
22
  number this script emits and the one compute-score.py weights agree to the digit.
23
23
 
24
24
  Input schema (one JSON object; a tool that did not run is null or omitted):
@@ -40,6 +40,10 @@ CLI usage (mirrors compute-score.py):
40
40
  uv run combine-external-scores.py --json-input '<JSON>' # explicit flag
41
41
  cat input.json | uv run combine-external-scores.py --stdin # piped input
42
42
 
43
+ --output <file> also writes the result to a file (UTF-8 JSON), the one
44
+ compute-score.py --external reads; a refused input removes a file left
45
+ there earlier.
46
+
43
47
  Exit codes:
44
48
  0 — score emitted successfully
45
49
  1 — no input / input could not be parsed as JSON
@@ -52,6 +56,7 @@ import argparse
52
56
  import json
53
57
  import math
54
58
  import sys
59
+ from pathlib import Path
55
60
 
56
61
  SKILL_CHECK = "skill-check"
57
62
  TESSL = "tessl"
@@ -121,8 +126,8 @@ def _build_parser():
121
126
  prog="combine-external-scores",
122
127
  description=(
123
128
  "Deterministic combined external-validation score "
124
- "(external-validators.md §4). Averages the skill-check and tessl "
125
- "review scores (or passes a single available score through) into the "
129
+ "(external-validators.md §4). Averages the skill-check and Tessl "
130
+ "Review scores (or passes a single available score through) into the "
126
131
  "`externalValidation` scoring input."
127
132
  ),
128
133
  formatter_class=argparse.RawDescriptionHelpFormatter,
@@ -148,9 +153,19 @@ def _build_parser():
148
153
  action="store_true",
149
154
  help="Read the JSON object from stdin.",
150
155
  )
156
+ parser.add_argument("--output", metavar="PATH", help="also write the result to this file")
151
157
  return parser
152
158
 
153
159
 
160
+ def _write_output(path, result):
161
+ target = Path(path)
162
+ if result.get("code") == "INVALID_INPUT":
163
+ target.unlink(missing_ok=True)
164
+ return
165
+ target.parent.mkdir(parents=True, exist_ok=True)
166
+ target.write_bytes((json.dumps(result, indent=2) + "\n").encode("utf-8"))
167
+
168
+
154
169
  def _resolve_input(args):
155
170
  if args.stdin:
156
171
  return sys.stdin.read()
@@ -180,6 +195,8 @@ def main(argv=None):
180
195
  return 1
181
196
 
182
197
  result = combine(data)
198
+ if args.output:
199
+ _write_output(args.output, result)
183
200
  print(json.dumps(result, indent=2))
184
201
  if isinstance(result, dict) and result.get("code") == "INVALID_INPUT":
185
202
  return 2