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,459 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """Signature Accuracy and Type Coverage for SKF test-skill, from checked per-file results.
7
+
8
+ coverage-check.md section 2 compares each documented signature with the
9
+ source and section 2b scores two categories from the comparison: Signature
10
+ Accuracy and Type Coverage, 22% and 14% of a contextual score. Which
11
+ signature differs is a judgment, made by a subagent that reads the source;
12
+ everything after it has one answer per input and runs here: the list of
13
+ signatures to compare, the schema check of each subagent result, the two
14
+ ratios and the gap records of the wrong signatures.
15
+
16
+ plan the comparisons to make. For each documented export with a
17
+ signature (an inventory entry of any kind but `method` that carries
18
+ `params`, `return_type` or `usage_signature`) that the source
19
+ surface lists, grouped by source file, with the line that defines
20
+ it in the source and the documented signature. Also the whole
21
+ documented-signature map, for the fallback scan that has no surface
22
+ lines yet.
23
+ score the categories. Reads every subagent result, checks its schema,
24
+ and computes:
25
+ Signature Accuracy = matching / compared * 100, where compared is
26
+ the documented signatures the whole surface lists (the `all`
27
+ set, with a fallback scan's exports_found) and matching those
28
+ no result names in signature_mismatches[]: a wrong signature is
29
+ wrong whichever set the denominator counts
30
+ Type Coverage = documented types / total types * 100, where
31
+ total types counts the names of the --surface-set set (the
32
+ denominator set coverage-check section 2b picked) whose kind is
33
+ a type kind (TYPE_KINDS: interface, type alias, enum and class,
34
+ with a Rust struct counted as a class and a trait as an
35
+ interface) and documented types those the inventory documents
36
+ A category with nothing to compare scores 100 (nothing documented
37
+ is wrong) and says so in `warnings`. Each signature mismatch of a
38
+ compared name becomes a Critical `signature-mismatch` record in the
39
+ gap ledger's record format (gap-ledger.py append --input reads
40
+ --gaps-output); a mismatch a result reports for any other name (one
41
+ the skill does not document, or the surface lacks) is listed in
42
+ `warnings` instead, and no gap.
43
+
44
+ Inputs, all files in the run folder:
45
+ --inventory the validated inventory validate-inventory.py wrote (--output)
46
+ --surface load-coverage-inputs.py surface output: `exports[]` (name,
47
+ kind, file, line, signatureLine) and the name `sets`;
48
+ --surface-set picks the set (default "all"): plan's
49
+ comparisons and score's Type Coverage
50
+ --results a subagent result, as the subagent returned it (a wrapping
51
+ markdown fence is stripped), repeatable. One object, or a
52
+ list of them:
53
+ {"file": "<source file>",
54
+ "exports_found": ["..."], (fallback scan only)
55
+ "types_found": ["..."], (fallback scan only)
56
+ "signature_mismatches": [
57
+ {"name", "line", "source_sig", "documented_sig", "issue"}]}
58
+
59
+ Rounding is compute-score.py's (JS-compatible half-up, two decimals).
60
+
61
+ Usage:
62
+ uv run score-signatures.py plan --inventory <file> [--surface <file>] [--output <file>]
63
+ uv run score-signatures.py score --inventory <file> --surface <file> [--surface-set <set>] \\
64
+ --results <file> [--results <file> ...] [--output <file>] [--gaps-output <file>]
65
+
66
+ Output (stdout; --output also writes it, --gaps-output writes the records):
67
+ plan: {"compared": N, "files": [{"file", "checks": [{"name", "line",
68
+ "signatureLine", "documented": {...}}]}], "documentedSignatures": {...}}
69
+ score: {"valid": true, "signatureAccuracy", "matchingSignatures",
70
+ "totalDocumented", "typeCoverage", "documentedTypes", "totalTypes",
71
+ "missingTypes": [...], "mismatches": [...], "gapRecords": [...],
72
+ "warnings": [...]}
73
+ or {"valid": false, "violations": [...]} when a result breaks the schema
74
+
75
+ Exit codes:
76
+ 0 the result was printed
77
+ 1 an input file cannot be read or is not the JSON it should be, or the
78
+ surface has no --surface-set set
79
+ 2 a subagent result breaks the schema (violations[] names each breach;
80
+ nothing is written to --output or --gaps-output)
81
+ """
82
+
83
+ from __future__ import annotations
84
+
85
+ import argparse
86
+ import importlib.util
87
+ import json
88
+ import math
89
+ import sys
90
+ from pathlib import Path
91
+
92
+ # Surface kinds that count toward total_types. A Rust struct is a class and a
93
+ # trait an interface; `type` is a type alias.
94
+ TYPE_KINDS = frozenset({"interface", "type", "enum", "class", "struct", "trait"})
95
+ SIGNATURE_FIELDS = ("params", "return_type", "usage_signature")
96
+ MISMATCH_FIELDS = ("name", "line", "source_sig", "documented_sig", "issue")
97
+
98
+ _INVENTORY_MODULE = None
99
+
100
+
101
+ def _inventory():
102
+ """validate-inventory.py beside this script: the fence rule lives there."""
103
+ global _INVENTORY_MODULE
104
+ if _INVENTORY_MODULE is None:
105
+ sibling = Path(__file__).resolve().parent / "validate-inventory.py"
106
+ spec = importlib.util.spec_from_file_location("skf_validate_inventory", sibling)
107
+ if spec is None or spec.loader is None or not sibling.is_file():
108
+ raise ImportError(f"validate-inventory.py not found beside {Path(__file__).name}")
109
+ module = importlib.util.module_from_spec(spec)
110
+ spec.loader.exec_module(module)
111
+ _INVENTORY_MODULE = module
112
+ return _INVENTORY_MODULE
113
+
114
+
115
+ class InputError(Exception):
116
+ """An input file that cannot be read, or is not the JSON it should be."""
117
+
118
+
119
+ def round2(value: float) -> float:
120
+ """Round to 2 decimals with JS-compatible half-up rounding (matches compute-score.py)."""
121
+ return math.floor(value * 100 + 0.5) / 100
122
+
123
+
124
+ def _read_text(path: str, flag: str) -> str:
125
+ try:
126
+ return Path(path).read_bytes().decode("utf-8-sig")
127
+ except (OSError, UnicodeDecodeError) as exc:
128
+ raise InputError(f"cannot read {flag} {path}: {exc}") from exc
129
+
130
+
131
+ def _read_json(path: str, flag: str):
132
+ try:
133
+ return json.loads(_read_text(path, flag))
134
+ except json.JSONDecodeError as exc:
135
+ raise InputError(f"{flag} {path} is not JSON: {exc.msg}") from exc
136
+
137
+
138
+ # --------------------------------------------------------------------------
139
+ # Inputs
140
+ # --------------------------------------------------------------------------
141
+
142
+
143
+ def load_inventory(path: str) -> list[dict]:
144
+ data = _read_json(path, "--inventory")
145
+ if isinstance(data, dict) and isinstance(data.get("inventory"), dict):
146
+ data = data["inventory"] # validate-inventory.py's whole result
147
+ if not isinstance(data, dict) or not isinstance(data.get("exports"), list):
148
+ raise InputError(f"--inventory {path} holds no `exports` list")
149
+ return [e for e in data["exports"] if isinstance(e, dict)]
150
+
151
+
152
+ def load_surface(path: str, surface_set: str) -> tuple[list[str], list[dict], list[str]]:
153
+ """(the chosen name set, every surface export record, the `all` set)."""
154
+ data = _read_json(path, "--surface")
155
+ sets = data.get("sets") if isinstance(data, dict) else None
156
+
157
+ def name_set(key: str) -> list[str]:
158
+ names = sets.get(key) if isinstance(sets, dict) else None
159
+ if not isinstance(names, list):
160
+ raise InputError(f"--surface {path} has no name set `{key}`")
161
+ return [n for n in names if isinstance(n, str) and n]
162
+
163
+ exports = data.get("exports") if isinstance(data.get("exports"), list) else []
164
+ return name_set(surface_set), [e for e in exports if isinstance(e, dict)], name_set("all")
165
+
166
+
167
+ def check_result(item: object, where: str) -> list[str]:
168
+ """The schema breaches of one per-file result object."""
169
+ if not isinstance(item, dict):
170
+ return [f"{where} is not a JSON object"]
171
+ errors = []
172
+ if not (isinstance(item.get("file"), str) and item["file"].strip()):
173
+ errors.append(f"{where}: `file` must be a non-empty string")
174
+ for key in ("exports_found", "types_found"):
175
+ value = item.get(key)
176
+ if value is not None and not (isinstance(value, list) and all(isinstance(v, str) for v in value)):
177
+ errors.append(f"{where}: `{key}` must be a list of strings when present")
178
+ mismatches = item.get("signature_mismatches")
179
+ if not isinstance(mismatches, list):
180
+ errors.append(f"{where}: `signature_mismatches` must be present and a list (may be empty)")
181
+ return errors
182
+ for i, entry in enumerate(mismatches):
183
+ at = f"{where}.signature_mismatches[{i}]"
184
+ if not isinstance(entry, dict):
185
+ errors.append(f"{at} is not an object")
186
+ continue
187
+ missing = [f for f in MISMATCH_FIELDS if f not in entry]
188
+ if missing:
189
+ errors.append(f"{at} missing field(s): {', '.join(missing)}")
190
+ continue
191
+ line = entry["line"]
192
+ if isinstance(line, bool) or not isinstance(line, int) or line < 1:
193
+ errors.append(f"{at}: `line` must be a line number (an integer from 1)")
194
+ for field in ("name", "source_sig", "documented_sig", "issue"):
195
+ if not (isinstance(entry[field], str) and entry[field].strip()):
196
+ errors.append(f"{at}: `{field}` must be a non-empty string")
197
+ return errors
198
+
199
+
200
+ def parse_result_text(text: str, where: str) -> tuple[list[dict], list[str]]:
201
+ """(the result objects, the schema breaches) of one subagent response."""
202
+ inner = _inventory().strip_fences(text)
203
+ try:
204
+ data = json.loads(inner)
205
+ except json.JSONDecodeError as exc:
206
+ return [], [f"{where} is not valid JSON: {exc.msg}"]
207
+ items = data if isinstance(data, list) else [data]
208
+ errors = []
209
+ for i, item in enumerate(items):
210
+ errors += check_result(item, where if len(items) == 1 else f"{where}[{i}]")
211
+ return ([] if errors else items), errors
212
+
213
+
214
+ def load_results(paths: list[str]) -> tuple[list[dict], list[str]]:
215
+ """Every result object of the --results files, and every schema breach."""
216
+ results, violations = [], []
217
+ for path in paths:
218
+ items, errors = parse_result_text(_read_text(path, "--results"), Path(path).name)
219
+ results += items
220
+ violations += errors
221
+ return results, violations
222
+
223
+
224
+ # --------------------------------------------------------------------------
225
+ # Plan and score
226
+ # --------------------------------------------------------------------------
227
+
228
+
229
+ def _has_signature(entry: dict) -> bool:
230
+ return any(
231
+ isinstance(entry.get(f), list) or (isinstance(entry.get(f), str) and entry[f].strip())
232
+ for f in SIGNATURE_FIELDS
233
+ )
234
+
235
+
236
+ def documented_signatures(inventory: list[dict]) -> dict[str, dict]:
237
+ """{name: its documented signature fields} for each documented export with one."""
238
+ out: dict[str, dict] = {}
239
+ for entry in inventory:
240
+ name = entry.get("name")
241
+ if entry.get("kind") == "method" or not (isinstance(name, str) and name) or name in out:
242
+ continue
243
+ if _has_signature(entry):
244
+ out[name] = {f: entry[f] for f in SIGNATURE_FIELDS if f in entry}
245
+ return out
246
+
247
+
248
+ def documented_names(inventory: list[dict]) -> set[str]:
249
+ return {e["name"] for e in inventory
250
+ if isinstance(e.get("name"), str) and e["name"] and e.get("kind") != "method"}
251
+
252
+
253
+ def _records_by_name(exports: list[dict]) -> dict[str, list[dict]]:
254
+ by_name: dict[str, list[dict]] = {}
255
+ for record in sorted(exports, key=lambda r: (str(r.get("file") or ""), r.get("line") or 0)):
256
+ name = record.get("name")
257
+ if isinstance(name, str) and name:
258
+ by_name.setdefault(name, []).append(record)
259
+ return by_name
260
+
261
+
262
+ def plan(inventory: list[dict], names: list[str] | None, exports: list[dict]) -> dict:
263
+ signatures = documented_signatures(inventory)
264
+ files: dict[str, list[dict]] = {}
265
+ compared = 0
266
+ if names is not None:
267
+ by_name = _records_by_name(exports)
268
+ for name in sorted(set(names) & set(signatures)):
269
+ record = (by_name.get(name) or [{}])[0]
270
+ compared += 1
271
+ files.setdefault(str(record.get("file") or ""), []).append({
272
+ "name": name,
273
+ "line": record.get("line"),
274
+ "signatureLine": record.get("signatureLine"),
275
+ "documented": signatures[name],
276
+ })
277
+ return {
278
+ "compared": compared,
279
+ "files": [{"file": f, "checks": checks} for f, checks in sorted(files.items())],
280
+ "documentedSignatures": signatures,
281
+ }
282
+
283
+
284
+ def _gap_record(m: dict) -> dict:
285
+ name, where = m["name"], f"{m['file']}:{m['line']}"
286
+ return {
287
+ "severity": "Critical",
288
+ "category": "signature-mismatch",
289
+ "title": f"Signature mismatch: {name}",
290
+ "source": where,
291
+ "export": name,
292
+ "issue": f"documented `{m['documented_sig']}`, source `{m['source_sig']}`: {m['issue']}",
293
+ "remediation": (f"Update the documented signature of `{name}` from `{m['documented_sig']}` "
294
+ f"to `{m['source_sig']}`, the signature the source defines at `{where}`."),
295
+ }
296
+
297
+
298
+ def score(inventory: list[dict], names: list[str], exports: list[dict], results: list[dict],
299
+ type_names: list[str] | None = None) -> dict:
300
+ """The two categories: Signature Accuracy over `names` (the whole surface),
301
+ Type Coverage over `type_names` when given, else over `names` too."""
302
+ by_name = _records_by_name(exports)
303
+ # A fallback scan reports its own names and types.
304
+ fallback_types = {n for r in results for n in r.get("types_found") or []}
305
+ surface = set(names) | {n for r in results for n in r.get("exports_found") or []}
306
+ documented = documented_names(inventory)
307
+ signatures = documented_signatures(inventory)
308
+ compared = surface & set(signatures)
309
+ warnings = []
310
+
311
+ mismatches = []
312
+ seen = set()
313
+ for result in results:
314
+ for entry in result["signature_mismatches"]:
315
+ key = (entry["name"], result["file"], entry["line"])
316
+ if key in seen:
317
+ continue
318
+ seen.add(key)
319
+ if entry["name"] not in compared:
320
+ warnings.append(f"{result['file']}:{entry['line']}: a signature mismatch for "
321
+ f"`{entry['name']}`, which is no documented signature on the source surface: "
322
+ "not scored, no gap")
323
+ continue
324
+ mismatches.append({"name": entry["name"], "file": result["file"], "line": entry["line"],
325
+ "source_sig": entry["source_sig"], "documented_sig": entry["documented_sig"],
326
+ "issue": entry["issue"]})
327
+ mismatches.sort(key=lambda m: (m["file"], m["line"], m["name"]))
328
+
329
+ wrong = {m["name"] for m in mismatches}
330
+ if compared:
331
+ signature_accuracy = round2((len(compared) - len(wrong)) / len(compared) * 100)
332
+ else:
333
+ signature_accuracy = 100.0
334
+ warnings.append("no documented signature is on the source surface: Signature Accuracy has "
335
+ "nothing to compare and scores 100")
336
+
337
+ def kind_of(name: str) -> str | None:
338
+ if name in fallback_types:
339
+ return "type"
340
+ return next((r.get("kind") for r in by_name.get(name, []) if r.get("kind")), None)
341
+
342
+ types = {n for n in (surface if type_names is None else set(type_names)) if kind_of(n) in TYPE_KINDS}
343
+ documented_types = types & documented
344
+ if types:
345
+ type_coverage = round2(len(documented_types) / len(types) * 100)
346
+ else:
347
+ type_coverage = 100.0
348
+ warnings.append("the source surface has no interface, type alias, enum or class: Type "
349
+ "Coverage has nothing to cover and scores 100")
350
+
351
+ return {
352
+ "valid": True,
353
+ "signatureAccuracy": signature_accuracy,
354
+ "matchingSignatures": len(compared) - len(wrong),
355
+ "totalDocumented": len(compared),
356
+ "typeCoverage": type_coverage,
357
+ "documentedTypes": len(documented_types),
358
+ "totalTypes": len(types),
359
+ "missingTypes": sorted(types - documented),
360
+ "mismatches": mismatches,
361
+ "gapRecords": [_gap_record(m) for m in mismatches],
362
+ "warnings": warnings,
363
+ }
364
+
365
+
366
+ # --------------------------------------------------------------------------
367
+ # CLI
368
+ # --------------------------------------------------------------------------
369
+
370
+
371
+ def _write_json(path: str, payload) -> None:
372
+ target = Path(path)
373
+ target.parent.mkdir(parents=True, exist_ok=True)
374
+ target.write_bytes((json.dumps(payload, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
375
+
376
+
377
+ def _cmd_plan(args: argparse.Namespace) -> tuple[dict, int]:
378
+ inventory = load_inventory(args.inventory)
379
+ names, exports = (None, [])
380
+ if args.surface is not None:
381
+ names, exports, _ = load_surface(args.surface, args.surface_set)
382
+ out = plan(inventory, names, exports)
383
+ if args.output:
384
+ _write_json(args.output, out)
385
+ return out, 0
386
+
387
+
388
+ def _cmd_score(args: argparse.Namespace) -> tuple[dict, int]:
389
+ inventory = load_inventory(args.inventory)
390
+ chosen, exports, names = load_surface(args.surface, args.surface_set)
391
+ results, violations = load_results(args.results or [])
392
+ if violations:
393
+ for path in (args.output, args.gaps_output):
394
+ if path:
395
+ Path(path).unlink(missing_ok=True)
396
+ return {"valid": False, "violations": violations}, 2
397
+ out = score(inventory, names, exports, results, None if args.surface_set == "all" else chosen)
398
+ if args.output:
399
+ _write_json(args.output, out)
400
+ if args.gaps_output:
401
+ _write_json(args.gaps_output, out["gapRecords"])
402
+ return out, 0
403
+
404
+
405
+ def _build_parser() -> argparse.ArgumentParser:
406
+ parser = argparse.ArgumentParser(
407
+ prog="score-signatures",
408
+ description="Signature Accuracy and Type Coverage from schema-checked per-file results.",
409
+ )
410
+ sub = parser.add_subparsers(dest="cmd", required=True)
411
+
412
+ p = sub.add_parser("plan", help="the signature comparisons for the subagents to make")
413
+ p.add_argument("--inventory", required=True, help="the validated inventory file")
414
+ p.add_argument("--surface", help="load-coverage-inputs.py surface output")
415
+ p.add_argument("--surface-set", default="all", help="the surface name set (default: all)")
416
+ p.add_argument("--output", help="also write the plan to this file")
417
+ p.set_defaults(func=_cmd_plan)
418
+
419
+ p = sub.add_parser("score", help="the two category scores and the mismatch gap records")
420
+ p.add_argument("--inventory", required=True, help="the validated inventory file")
421
+ p.add_argument("--surface", required=True, help="load-coverage-inputs.py surface output")
422
+ p.add_argument("--surface-set", default="all",
423
+ help="the surface name set Type Coverage counts (default: all)")
424
+ p.add_argument("--results", action="append", help="a subagent result file (repeatable)")
425
+ p.add_argument("--output", help="also write the scores to this file")
426
+ p.add_argument("--gaps-output", help="write the gap ledger records to this file")
427
+ p.set_defaults(func=_cmd_score)
428
+ return parser
429
+
430
+
431
+ def main(argv: list[str] | None = None) -> int:
432
+ args = _build_parser().parse_args(argv)
433
+ try:
434
+ out, code = args.func(args)
435
+ except (InputError, ImportError) as exc:
436
+ print(f"error: {exc}", file=sys.stderr)
437
+ return 1
438
+ print(json.dumps(out, indent=2))
439
+ return code
440
+
441
+
442
+ def _force_utf8(*streams) -> None:
443
+ """Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
444
+
445
+ A Windows console pipes them as cp1252, which cannot print every character
446
+ of the --help text, so --help would stop with UnicodeEncodeError.
447
+ """
448
+ for stream in streams:
449
+ if hasattr(stream, "reconfigure"):
450
+ errors = getattr(stream, "errors", None)
451
+ if errors is None:
452
+ stream.reconfigure(encoding="utf-8")
453
+ else:
454
+ stream.reconfigure(encoding="utf-8", errors=errors)
455
+
456
+
457
+ if __name__ == "__main__":
458
+ _force_utf8(sys.stdout, sys.stderr)
459
+ sys.exit(main())
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env python3
2
+ # /// script
3
+ # requires-python = ">=3.11"
4
+ # dependencies = []
5
+ # ///
6
+ """Print the stdin payload of a shared SKF helper, read from the files on disk.
7
+
8
+ coverage-check.md §0b runs skf-detect-workspaces.py (is the local source a
9
+ monorepo?), a shared helper that takes one JSON payload on stdin and reads
10
+ no file itself. A payload the model writes by hand can cut a file short or
11
+ escape it wrongly, and the helper then fails or answers for the wrong text.
12
+ This script reads the files and prints the payload, and the step pipes it
13
+ into the helper:
14
+
15
+ uv run stage-helper-payload.py detect-workspaces --source-root <dir> \\
16
+ | uv run skf-detect-workspaces.py
17
+
18
+ (The Quick-tier export scan needs no payload: skf-extract-public-api.py
19
+ --mode quick reads its files itself, with --manifest-file and --entry-file.)
20
+
21
+ detect-workspaces prints {"tree": [...], "manifests": {...}}:
22
+
23
+ tree every file below --source-root, as a POSIX path relative to
24
+ it, sorted. Folders named in SKIPPED_DIRS (installed
25
+ dependencies, virtual environments, build output, caches) and
26
+ hidden folders (a name that starts with ".") are not read, and a
27
+ linked folder is not followed.
28
+ manifests the text of each file directly in --source-root that is not
29
+ hidden, holds at most MAX_ROOT_FILE_BYTES and reads as UTF-8, by
30
+ its name. The detector reads the root manifests it knows
31
+ (package.json, pnpm-workspace.yaml, lerna.json, Cargo.toml ...)
32
+ and ignores the rest, so this script keeps no list of their
33
+ names to fall out of step with the detector's.
34
+
35
+ The payload is one line of ASCII JSON on stdout, so a pipe carries the same
36
+ bytes on every platform.
37
+
38
+ Exit codes:
39
+ 0 the payload is printed
40
+ 1 --source-root is not a folder; one JSON line {"status": "error",
41
+ "error": ...} goes to stderr and nothing to stdout, so the helper the
42
+ payload was piped into reads no input and fails too
43
+ 2 bad arguments (argparse: a missing or unknown argument, usage on stderr)
44
+ """
45
+
46
+ from __future__ import annotations
47
+
48
+ import argparse
49
+ import json
50
+ import os
51
+ import sys
52
+ from pathlib import Path
53
+
54
+ # Folders that hold no workspace member of the repository itself.
55
+ SKIPPED_DIRS = frozenset({
56
+ "node_modules", "bower_components", "jspm_packages", "vendor", "Pods",
57
+ "venv", "site-packages", "__pycache__", "target",
58
+ })
59
+ MAX_ROOT_FILE_BYTES = 1024 * 1024
60
+
61
+
62
+ class StageError(Exception):
63
+ """A path the payload cannot be built from."""
64
+
65
+
66
+ def _source_root(value: str) -> Path:
67
+ root = Path(value)
68
+ if not root.is_dir():
69
+ raise StageError(f"--source-root is not a folder: {value}")
70
+ return root
71
+
72
+
73
+ # --------------------------------------------------------------------------
74
+ # detect-workspaces
75
+ # --------------------------------------------------------------------------
76
+
77
+
78
+ def _skipped(name: str) -> bool:
79
+ return name.startswith(".") or name in SKIPPED_DIRS
80
+
81
+
82
+ def list_tree(root: Path) -> list[str]:
83
+ """Every file below `root` as a sorted POSIX path, outside skipped and linked folders."""
84
+ tree = []
85
+ for dirpath, dirnames, filenames in os.walk(root):
86
+ here = Path(dirpath)
87
+ dirnames[:] = [d for d in dirnames if not _skipped(d) and not (here / d).is_symlink()]
88
+ for name in filenames:
89
+ tree.append((here / name).relative_to(root).as_posix())
90
+ return sorted(tree)
91
+
92
+
93
+ def root_texts(root: Path) -> dict[str, str]:
94
+ """The text of each file directly in `root` that is not hidden, small enough, and UTF-8."""
95
+ texts = {}
96
+ for path in sorted(root.iterdir()):
97
+ if path.name.startswith(".") or path.is_symlink() or not path.is_file():
98
+ continue
99
+ if path.stat().st_size > MAX_ROOT_FILE_BYTES:
100
+ continue
101
+ try:
102
+ texts[path.name] = path.read_bytes().decode("utf-8-sig")
103
+ except UnicodeDecodeError:
104
+ continue
105
+ return texts
106
+
107
+
108
+ def workspaces_payload(source_root: str) -> dict:
109
+ root = _source_root(source_root)
110
+ return {"tree": list_tree(root), "manifests": root_texts(root)}
111
+
112
+
113
+ # --------------------------------------------------------------------------
114
+ # CLI
115
+ # --------------------------------------------------------------------------
116
+
117
+
118
+ def _cmd_detect_workspaces(args: argparse.Namespace) -> dict:
119
+ return workspaces_payload(args.source_root)
120
+
121
+
122
+ def _build_parser() -> argparse.ArgumentParser:
123
+ parser = argparse.ArgumentParser(
124
+ prog="stage-helper-payload",
125
+ description="Print a shared helper's stdin payload, read from the files on disk.",
126
+ )
127
+ sub = parser.add_subparsers(dest="cmd", required=True)
128
+
129
+ p = sub.add_parser("detect-workspaces", help="the tree and root files for skf-detect-workspaces.py")
130
+ p.add_argument("--source-root", required=True, help="the local source folder")
131
+ p.set_defaults(func=_cmd_detect_workspaces)
132
+
133
+ return parser
134
+
135
+
136
+ def main(argv: list[str] | None = None) -> int:
137
+ args = _build_parser().parse_args(argv)
138
+ try:
139
+ payload = args.func(args)
140
+ except (StageError, OSError) as exc:
141
+ print(json.dumps({"status": "error", "error": str(exc)}), file=sys.stderr)
142
+ return 1
143
+ print(json.dumps(payload))
144
+ return 0
145
+
146
+
147
+ if __name__ == "__main__":
148
+ sys.exit(main())