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
@@ -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 schema validation of the §1 subagent inventory JSON.
6
6
 
@@ -13,19 +13,58 @@ and types, assert each export entry is a dict with a non-empty `name` and a
13
13
  `kind` drawn from a fixed enum, and assert each cross-check mismatch carries its
14
14
  five fields — with exactly one correct pass/fail per input and no
15
15
  interpretation of meaning. It is therefore script work, not prompt work; the
16
- prompt reads this verdict and owns the HALT decision plus the separate
17
- grep ground-truth spot-check.
16
+ prompt reads this verdict and owns only the HALT decision.
17
+
18
+ With --skill-package it also runs the ground-truth spot-check (coverage-check.md
19
+ §1a): it sorts the exports by name (a stable sort), samples the first, the
20
+ middle and the last (`min(3, exportsCount)` names, indices
21
+ `[0, len//2, len-1]`), and looks each sampled name up in the package's
22
+ SKILL.md and in every file the inventory's `references` list names, with
23
+ the documented-name match below. A sampled name none of them writes is a
24
+ fabricated export: the inventory is then invalid, each absent name is a
25
+ violation, and the prompt re-dispatches the subagent with them, as it does
26
+ for a schema violation. No grep runs in the prompt, so an export named
27
+ `$state` or `a.b` is never a shell variable or a regex.
18
28
 
19
29
  Distinct from reconcile-coverage.py (coverage numerator arithmetic) and
20
30
  compute-score.py (weight tables): this script only validates the *shape* of the
21
31
  subagent inventory and, on success, echoes the fence-stripped parsed inventory
22
32
  back so the prompt consumes it directly instead of re-parsing the raw response.
23
33
 
34
+ It also reports each documented item's completeness, the docs-only Export
35
+ Coverage rule: an item (one name and kind) is complete when it has a
36
+ non-empty `description` and, for a function or a method, `params` (a string
37
+ or a list, empty when the function takes none) and a non-empty
38
+ `return_type`. A missing description makes an item incomplete; it never
39
+ makes the inventory invalid. reconcile-coverage.py's docsOnly branch scores
40
+ with the same rule.
41
+
42
+ Shared helpers. The other coverage scripts load these from this file rather
43
+ than keep copies: `strip_fences` (score-signatures.py), `completeness`
44
+ (reconcile-coverage.py) and the documented-name match, `load_doc_text` and
45
+ `names_present` (reconcile-coverage.py, verify-declared-numerator.py,
46
+ load-coverage-inputs.py). A name is documented when SKILL.md or a .md file
47
+ directly in references/ writes it as a fixed string, case-sensitive, never
48
+ inside a longer identifier (the Export match rule of
49
+ skf-scan-skill-md-structure.py usage-scope): on a side where the name ends
50
+ in a letter, digit, `_` or `$`, the next character may not be one of those.
51
+ So `get` is found in `get(url)` but not in `target` or `getAll`.
52
+
24
53
  CLI usage (mirrors reconcile-coverage.py):
54
+ uv run validate-inventory.py --input <response file> [--output <inventory file>]
55
+ [--skill-package <package folder>]
25
56
  uv run validate-inventory.py '<raw response>' # positional
26
57
  uv run validate-inventory.py --json-input '<raw response>' # explicit flag
27
58
  echo '<raw response>' | uv run validate-inventory.py --stdin # piped input
28
59
 
60
+ --input reads the raw response from a file (UTF-8): coverage-check.md saves
61
+ the subagent's response with the Write tool and passes its path, so a quote
62
+ or an apostrophe in a description never meets a shell. --output writes the
63
+ validated inventory object to a file (UTF-8 JSON) when it is valid, for
64
+ the steps that read it by path; an invalid inventory removes a file left
65
+ there by an earlier attempt. --skill-package names the skill package under
66
+ test (the folder that holds SKILL.md) and adds the spot-check.
67
+
29
68
  Input: the subagent's RAW response text (JSON, optionally wrapped in a markdown
30
69
  code fence — a leading line of three backticks with an optional language tag and
31
70
  a trailing line of three backticks are stripped before parsing).
@@ -36,21 +75,36 @@ Output (stdout, one object):
36
75
  "violations": [<str>, ...], # human-readable failures; empty when valid
37
76
  "rejectedCount": <int>, # count of malformed exports[] entries
38
77
  "exportsCount": <int>, # len(exports) when parsed; 0 otherwise
39
- "inventory": {...} | null # fence-stripped parsed inventory when valid; null otherwise
78
+ "inventory": {...} | null, # fence-stripped parsed inventory when valid; null otherwise
79
+ "completeness": { # present when valid
80
+ "total": <int>, # documented items, one per name and kind
81
+ "complete": <int>,
82
+ "incomplete": [{"name", "kind", "missing": ["description", "params", "return_type"]}]
83
+ },
84
+ "spotCheck": { # with --skill-package, once the schema passed
85
+ "sampled": [<name>, ...], # the sampled names, in sample order
86
+ "absent": [<name>, ...], # the sampled names no file writes
87
+ "files": [<path>, ...], # the files read, relative to the package
88
+ "unread": [<path>, ...] # listed references that are missing,
89
+ } # unreadable or outside the package
40
90
  }
41
91
 
42
92
  Exit codes:
43
- 0 — inventory valid
44
- 1 — no input provided (usage error)
45
- 2 — inventory invalid (parse failure or schema violation; result JSON emitted)
93
+ 0 inventory valid (and every sampled name found)
94
+ 1 no input provided, the --input file cannot be read, or the
95
+ --skill-package folder holds no readable SKILL.md (usage error)
96
+ 2 inventory invalid (parse failure, schema violation or a sampled name
97
+ absent; result JSON emitted)
46
98
  """
47
99
 
48
100
  from __future__ import annotations
49
101
 
50
102
  import argparse
51
103
  import json
104
+ import os
52
105
  import re
53
106
  import sys
107
+ from pathlib import Path
54
108
 
55
109
  # Fixed enum for exports[].kind — the constructs SKF documents across languages
56
110
  # and skill types: JS/TS (function/class/type/constant/hook/interface/method),
@@ -80,6 +134,15 @@ REQUIRED_MISMATCH_FIELDS = (
80
134
  "issue",
81
135
  )
82
136
 
137
+ # Kinds whose documented item also needs its parameters and return type to
138
+ # be complete (the docs-only Export Coverage rule).
139
+ SIGNATURE_KINDS = ("function", "method")
140
+
141
+ # Characters that continue an identifier in the languages SKF documents
142
+ # (`$` for JS/TS names such as `$state`).
143
+ IDENT_CHARS = r"\w$"
144
+ _IDENT_RE = re.compile(f"[{IDENT_CHARS}]")
145
+
83
146
  _FENCE_OPEN = re.compile(r"^```[A-Za-z0-9_-]*$")
84
147
  _FENCE_CLOSE = re.compile(r"^```$")
85
148
 
@@ -102,11 +165,174 @@ def strip_fences(text):
102
165
  return text
103
166
 
104
167
 
105
- def validate_inventory(raw):
106
- """Pure structural validation of the subagent inventory response.
168
+ # --- Documented-name match --------------------------------------------------
169
+
170
+
171
+ def name_pattern(name):
172
+ """Regex for `name` as a fixed string that is never part of a longer identifier.
173
+
174
+ The guard only applies on a side where the name itself ends in an
175
+ identifier character, so `.then` still matches inside `promise.then` and
176
+ `Type::method` matches as written.
177
+ """
178
+ body = re.escape(name)
179
+ if _IDENT_RE.match(name[0]):
180
+ body = f"(?<![{IDENT_CHARS}])" + body
181
+ if _IDENT_RE.match(name[-1]):
182
+ body += f"(?![{IDENT_CHARS}])"
183
+ return re.compile(body)
184
+
185
+
186
+ def load_doc_text(skill_package_path):
187
+ """Concatenate SKILL.md ∪ references/*.md text for name lookups.
188
+
189
+ Deterministic: references are read in sorted order, a references/
190
+ subfolder does not count. Reads as UTF-8 so non-ASCII exports do not
191
+ mojibake on Windows (cp1252 default).
192
+ """
193
+ root = Path(skill_package_path)
194
+ parts = []
195
+ skill_md = root / "SKILL.md"
196
+ if skill_md.is_file():
197
+ parts.append(skill_md.read_text(encoding="utf-8"))
198
+ refs_dir = root / "references"
199
+ if refs_dir.is_dir():
200
+ for ref in sorted(refs_dir.glob("*.md")):
201
+ parts.append(ref.read_text(encoding="utf-8"))
202
+ return "\n".join(parts)
203
+
204
+
205
+ def names_present(names, doc_text):
206
+ """Return the sorted, de-duplicated set of `names` the doc text writes."""
207
+ present = {n for n in names if isinstance(n, str) and n and name_pattern(n).search(doc_text)}
208
+ return sorted(present)
209
+
210
+
211
+ # --- Spot-check -------------------------------------------------------------
212
+
213
+
214
+ def sample_names(exports):
215
+ """The names the spot-check samples: first, middle and last after a stable sort by name.
216
+
217
+ >>> sample_names([{"name": n} for n in ("d", "a", "c", "b", "e")])
218
+ ['a', 'c', 'e']
219
+ >>> sample_names([{"name": "b"}, {"name": "a"}])
220
+ ['a', 'b']
221
+ >>> sample_names([])
222
+ []
223
+ """
224
+ named = sorted((e["name"] for e in exports or []
225
+ if isinstance(e, dict) and isinstance(e.get("name"), str) and e["name"]))
226
+ picks = []
227
+ for index in (0, len(named) // 2, len(named) - 1):
228
+ if named and named[index] not in picks:
229
+ picks.append(named[index])
230
+ return picks
231
+
232
+
233
+ class PackageError(Exception):
234
+ """The --skill-package folder holds no readable SKILL.md: exit 1."""
235
+
236
+
237
+ def _inside(path, root):
238
+ path, root = os.path.normcase(path), os.path.normcase(root)
239
+ return path == root or path.startswith(root.rstrip(os.sep) + os.sep)
240
+
241
+
242
+ def spot_check(inventory, skill_package_path):
243
+ """Look the sampled export names up in SKILL.md and the inventory's references.
244
+
245
+ The file set is the package's SKILL.md plus each `references` entry,
246
+ resolved against the package (a split-body skill documents some exports
247
+ only in a reference file). A listed file that is missing, unreadable or
248
+ resolves outside the package is not read and is named in `unread`.
249
+ """
250
+ root = Path(skill_package_path)
251
+ real_root = os.path.realpath(root)
252
+ try:
253
+ texts = [(root / "SKILL.md").read_bytes().decode("utf-8-sig")]
254
+ except (OSError, UnicodeDecodeError) as exc:
255
+ raise PackageError(f"cannot read {(root / 'SKILL.md').as_posix()}: {exc}") from exc
256
+ files, unread = ["SKILL.md"], []
257
+ listed = inventory.get("references")
258
+ for ref in listed if isinstance(listed, list) else []:
259
+ if not isinstance(ref, str) or not ref.strip() or ref in files or ref in unread:
260
+ continue
261
+ target = os.path.realpath(root / ref)
262
+ try:
263
+ if not _inside(target, real_root):
264
+ raise OSError("outside the package")
265
+ texts.append(Path(target).read_bytes().decode("utf-8-sig"))
266
+ files.append(ref)
267
+ except (OSError, UnicodeDecodeError):
268
+ unread.append(ref)
269
+ doc_text = "\n".join(texts)
270
+ sampled = sample_names(inventory.get("exports"))
271
+ found = set(names_present(sampled, doc_text))
272
+ return {
273
+ "sampled": sampled,
274
+ "absent": [name for name in sampled if name not in found],
275
+ "files": files,
276
+ "unread": unread,
277
+ }
278
+
279
+
280
+ # --- Completeness -------------------------------------------------------------
281
+
282
+
283
+ def _filled(value):
284
+ return isinstance(value, str) and bool(value.strip())
285
+
286
+
287
+ def missing_fields(entry):
288
+ """The fields a documented item lacks to be complete (docs-only rule)."""
289
+ missing = []
290
+ if not _filled(entry.get("description")):
291
+ missing.append("description")
292
+ if entry.get("kind") in SIGNATURE_KINDS:
293
+ if not isinstance(entry.get("params"), (str, list)):
294
+ missing.append("params")
295
+ if not _filled(entry.get("return_type")):
296
+ missing.append("return_type")
297
+ return missing
298
+
299
+
300
+ def completeness(exports):
301
+ """Count complete documented items: one item per name and kind.
302
+
303
+ An item the inventory lists twice is complete when either entry is.
304
+ Entries without a string name or a known kind are not items.
305
+ """
306
+ items = {}
307
+ for entry in exports or []:
308
+ if not isinstance(entry, dict):
309
+ continue
310
+ name, kind = entry.get("name"), entry.get("kind")
311
+ if not (isinstance(name, str) and name) or kind not in VALID_KINDS:
312
+ continue
313
+ missing = missing_fields(entry)
314
+ key = (name, kind)
315
+ if key not in items or not missing:
316
+ items[key] = missing
317
+ incomplete = [{"name": name, "kind": kind, "missing": missing}
318
+ for (name, kind), missing in items.items() if missing]
319
+ return {
320
+ "total": len(items),
321
+ "complete": len(items) - len(incomplete),
322
+ "incomplete": incomplete,
323
+ }
324
+
325
+
326
+ # --- Validation ---------------------------------------------------------------
327
+
328
+
329
+ def validate_inventory(raw, skill_package_path=None):
330
+ """Structural validation of the subagent inventory response, then the spot-check.
107
331
 
108
332
  `raw` is the raw response text. Returns the result dict documented in the
109
- module docstring. Deterministic: identical input yields an identical verdict.
333
+ module docstring. With `skill_package_path`, an inventory that passed the
334
+ schema is spot-checked against the package (spot_check). Deterministic:
335
+ identical input yields an identical verdict.
110
336
  """
111
337
  violations = []
112
338
  inner = strip_fences(raw)
@@ -177,14 +403,28 @@ def validate_inventory(raw):
177
403
  f"cross_check_mismatches[{idx}] missing field(s): {', '.join(missing)}"
178
404
  )
179
405
 
406
+ checked = None
407
+ if not violations and skill_package_path is not None:
408
+ checked = spot_check(data, skill_package_path)
409
+ for name in checked["absent"]:
410
+ violations.append(
411
+ f"spot-check: `{name}` is listed as an export, but SKILL.md and the "
412
+ f"listed reference files never write it"
413
+ )
414
+
180
415
  valid = not violations
181
- return {
416
+ result = {
182
417
  "valid": valid,
183
418
  "violations": violations,
184
419
  "rejectedCount": rejected,
185
420
  "exportsCount": len(exports),
186
421
  "inventory": data if valid else None,
187
422
  }
423
+ if valid:
424
+ result["completeness"] = completeness(exports)
425
+ if checked is not None:
426
+ result["spotCheck"] = checked
427
+ return result
188
428
 
189
429
 
190
430
  # --- CLI --------------------------------------------------------------------
@@ -196,8 +436,9 @@ def _build_parser():
196
436
  description=(
197
437
  "Deterministic schema validation of the §1 subagent inventory JSON "
198
438
  "(coverage-check.md §1a). Strips wrapping fences, parses, and asserts "
199
- "the required-keys/types/enum/mismatch-field contract, returning "
200
- "{valid, violations, rejectedCount, exportsCount, inventory}."
439
+ "the required-keys/types/enum/mismatch-field contract, then with "
440
+ "--skill-package spot-checks sampled names against the package, returning "
441
+ "{valid, violations, rejectedCount, exportsCount, inventory, completeness, spotCheck}."
201
442
  ),
202
443
  formatter_class=argparse.RawDescriptionHelpFormatter,
203
444
  )
@@ -217,12 +458,30 @@ def _build_parser():
217
458
  action="store_true",
218
459
  help="Read the raw subagent response from stdin.",
219
460
  )
461
+ src.add_argument(
462
+ "--input",
463
+ dest="input_file",
464
+ metavar="PATH",
465
+ help="Read the raw subagent response from this file (UTF-8).",
466
+ )
467
+ parser.add_argument(
468
+ "--output",
469
+ metavar="PATH",
470
+ help="Write the validated inventory to this file when it is valid.",
471
+ )
472
+ parser.add_argument(
473
+ "--skill-package",
474
+ metavar="DIR",
475
+ help="Spot-check sampled names against this package's SKILL.md and listed references.",
476
+ )
220
477
  return parser
221
478
 
222
479
 
223
480
  def _resolve_input(args):
224
481
  if args.stdin:
225
482
  return sys.stdin.read()
483
+ if args.input_file is not None:
484
+ return Path(args.input_file).read_bytes().decode("utf-8-sig")
226
485
  if args.raw_input_flag is not None:
227
486
  return args.raw_input_flag
228
487
  if args.raw_input is not None:
@@ -230,22 +489,58 @@ def _resolve_input(args):
230
489
  return ""
231
490
 
232
491
 
492
+ def write_json(path, payload):
493
+ """Write `payload` as indented UTF-8 JSON with a final newline."""
494
+ target = Path(path)
495
+ target.parent.mkdir(parents=True, exist_ok=True)
496
+ target.write_bytes((json.dumps(payload, indent=2, ensure_ascii=False) + "\n").encode("utf-8"))
497
+
498
+
233
499
  def main(argv=None):
234
500
  parser = _build_parser()
235
501
  args = parser.parse_args(argv)
236
- raw = _resolve_input(args)
502
+ try:
503
+ raw = _resolve_input(args)
504
+ except (OSError, UnicodeDecodeError) as exc:
505
+ print(f"error: cannot read --input {args.input_file}: {exc}", file=sys.stderr)
506
+ return 1
237
507
  if not raw.strip():
238
508
  parser.print_usage(file=sys.stderr)
239
509
  print(
240
- "error: no input provided (positional arg, --json-input, or --stdin)",
510
+ "error: no input provided (positional arg, --json-input, --stdin or --input)",
241
511
  file=sys.stderr,
242
512
  )
243
513
  return 1
244
514
 
245
- result = validate_inventory(raw)
515
+ try:
516
+ result = validate_inventory(raw, args.skill_package)
517
+ except PackageError as exc:
518
+ print(f"error: --skill-package: {exc}", file=sys.stderr)
519
+ return 1
520
+ if args.output:
521
+ if result["valid"]:
522
+ write_json(args.output, result["inventory"])
523
+ else:
524
+ Path(args.output).unlink(missing_ok=True)
246
525
  print(json.dumps(result, indent=2))
247
526
  return 0 if result["valid"] else 2
248
527
 
249
528
 
529
+ def _force_utf8(*streams) -> None:
530
+ """Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
531
+
532
+ A Windows console pipes them as cp1252, which cannot print every character
533
+ of the --help text, so --help would stop with UnicodeEncodeError.
534
+ """
535
+ for stream in streams:
536
+ if hasattr(stream, "reconfigure"):
537
+ errors = getattr(stream, "errors", None)
538
+ if errors is None:
539
+ stream.reconfigure(encoding="utf-8")
540
+ else:
541
+ stream.reconfigure(encoding="utf-8", errors=errors)
542
+
543
+
250
544
  if __name__ == "__main__":
545
+ _force_utf8(sys.stdout, sys.stderr)
251
546
  raise SystemExit(main())