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,43 +1,94 @@
1
1
  # /// script
2
- # requires-python = ">=3.9"
2
+ # requires-python = ">=3.11"
3
3
  # dependencies = ["pyyaml"]
4
4
  # ///
5
- """Campaign Render Kickoff — fill the mechanical kickoff-template placeholders.
6
-
7
- step-05 emits a kickoff message per Tier-A skill. Most of its placeholders are
8
- direct field copies from state + brief (campaign name, stage, quality gate,
9
- skill identity, repo, pin, commit, the dependency-status table, the workaround
10
- list) — mechanical substitution that an LLM should not hand-perform 15× per
11
- campaign. This script renders those deterministically and leaves the three
12
- judgment slots untouched for the LLM to fill in context:
13
-
14
- {{brief_summary}} — concise summary of the brief target entry
15
- {{persistent_facts}} — campaign-wide facts resolved in On Activation
16
- {{directive_content}} — raw directive file content
5
+ """Campaign Render Kickoff: render the per-skill kickoff message, every slot.
6
+
7
+ step-05 emits a kickoff message per Tier-A skill. Each placeholder is data the
8
+ campaign already holds, so this script fills all of them and step-05 presents
9
+ the output as it is. Re-typing them for each of 15+ skills cost output tokens
10
+ and risked paraphrasing or cutting the operator's directive.
11
+
12
+ - From state and brief: campaign name, stage, quality gate, skill identity,
13
+ repo, pin, commit and the dependency-status table.
14
+ - {{workarounds_list}}: one bullet per workaround the pre-apply helper
15
+ applied, read from its log (--workarounds-file, the preapply-log.json
16
+ skf-preapply.py writes): the file it changed, the fingerprint and its
17
+ fix, and the severity. Without the file, the skill's workarounds_applied
18
+ from state.
19
+ - {{brief_summary}}: the skill's target entry in the campaign brief, every
20
+ field it carries (a language or scope hint included).
21
+ - {{directive_content}}: the directive file byte for byte, whatever its
22
+ encoding, or "No directive configured" when --directive-file is absent or
23
+ names no file.
24
+ - {{persistent_facts}}: one bullet per persistent fact, or "None". An entry
25
+ `file:<path-or-glob>` adds the content of each file it names, with
26
+ `{project-root}` replaced by --project-root; an entry `!<entry>` adds
27
+ nothing and drops every earlier entry equal to `<entry>`, so a team or
28
+ personal override drops a bundled default
29
+ (`!file:{project-root}/**/project-context.md`); any other entry is a fact
30
+ sentence.
17
31
 
18
32
  CLI:
19
33
  uv run campaign-render-kickoff.py --state-file <p> --brief-file <p> \
20
- --skill <name> --template <p> [--workarounds '<json-list>']
34
+ --skill <name> --template <p> [--workarounds-file <preapply-log.json>] \
35
+ [--facts-json '<json-list>' | --facts-json -] [--project-root <p>] \
36
+ [--directive-file <p>]
21
37
 
22
- Output: the rendered kickoff markdown on stdout (judgment slots preserved).
38
+ `--facts-json -` reads the facts from stdin: a JSON list, or the object the
39
+ customization resolver prints for `--key workflow.persistent_facts`.
40
+
41
+ Output: the rendered kickoff markdown on stdout, as UTF-8 bytes. The template
42
+ is filled in one pass, so a `{{...}}` inside the directive or a fact is kept
43
+ as written.
23
44
 
24
45
  Exit codes:
25
46
  0 rendered
26
- 2 error (missing file, bad YAML, skill/target not found, bad --workarounds)
47
+ 2 error, as {"error", "code"} on stderr:
48
+ STATE_NOT_FOUND, BRIEF_NOT_FOUND, TEMPLATE_NOT_FOUND
49
+ the input file is missing
50
+ STATE_UNREADABLE, BRIEF_UNREADABLE, TEMPLATE_UNREADABLE
51
+ it cannot be read as UTF-8 text
52
+ PARSE_ERROR state or brief is not a YAML mapping
53
+ SKILL_NOT_FOUND the skill is not in state
54
+ BAD_WORKAROUNDS --workarounds-file cannot be read, or holds no
55
+ applied[] list of workarounds
56
+ BAD_FACTS --facts-json is not a list of strings (nor the
57
+ resolver's object), --project-root is not a
58
+ directory, or a `file:` entry keeps a placeholder
59
+ FACTS_FILE_NOT_FOUND a `file:` path with no glob character names no
60
+ file (a glob that matches nothing adds no fact)
61
+ FACTS_UNREADABLE a facts file cannot be read
62
+ DIRECTIVE_UNREADABLE the directive file cannot be read
27
63
  """
28
64
 
29
65
  from __future__ import annotations
30
66
 
31
67
  import argparse
68
+ import glob
32
69
  import json
70
+ import re
33
71
  import sys
34
72
  from pathlib import Path
35
73
  from typing import Any, Dict, List, Optional
36
74
 
37
75
  import yaml
38
76
 
39
- # Placeholders this script intentionally leaves for the LLM to fill.
40
- JUDGMENT_SLOTS = ("{{brief_summary}}", "{{persistent_facts}}", "{{directive_content}}")
77
+ NO_DIRECTIVE = "No directive configured"
78
+ NO_FACTS = "None"
79
+ FILE_PREFIX = "file:"
80
+ DROP_PREFIX = "!"
81
+ PROJECT_ROOT = "{project-root}"
82
+ # The key the customization resolver prints for --key workflow.persistent_facts
83
+ # (it prints {} when no layer sets it).
84
+ RESOLVER_KEY = "workflow.persistent_facts"
85
+ GLOB_CHARS = ("*", "?", "[")
86
+ # Target fields shown first, in this order; any other field of the entry
87
+ # follows in the entry's own order.
88
+ BRIEF_FIELDS = ("name", "repo_url", "tier", "pin", "depends_on")
89
+ SLOT_RE = re.compile(r"\{\{[a-z_]+\}\}")
90
+ # A placeholder left in a `file:` entry would match no file.
91
+ UNRESOLVED_RE = re.compile(r"\{[A-Za-z][\w-]*\}")
41
92
 
42
93
 
43
94
  def _err(message: str, code: str) -> int:
@@ -64,10 +115,134 @@ def _dependency_status_table(skill: Dict[str, Any], skill_map: Dict[str, Dict[st
64
115
  return "\n".join(rows)
65
116
 
66
117
 
67
- def _workarounds_list(workarounds: List[str]) -> str:
118
+ def _code(text: Any) -> str:
119
+ """Inline code that survives a backtick inside the text."""
120
+ text = str(text)
121
+ return f"`` {text} ``" if "`" in text else f"`{text}`"
122
+
123
+
124
+ def _workaround(entry: Any) -> str:
125
+ """One applied workaround: a pre-apply log entry, or a string from state."""
126
+ if not isinstance(entry, dict):
127
+ return str(entry)
128
+ line = f"{_code(entry.get('fingerprint', ''))} replaced with {_code(entry.get('fix', ''))}"
129
+ if entry.get("file"):
130
+ line = f"{entry['file']}: {line}"
131
+ if entry.get("severity"):
132
+ line += f" ({entry['severity']} severity)"
133
+ return line
134
+
135
+
136
+ def _workarounds_list(workarounds: List[Any]) -> str:
68
137
  if not workarounds:
69
138
  return "None"
70
- return "\n".join(f"- {w}" for w in workarounds)
139
+ return "\n".join(f"- {_workaround(w)}" for w in workarounds)
140
+
141
+
142
+ def read_workarounds(path: str) -> List[Any]:
143
+ """The applied[] list of a pre-apply log. Raises ValueError on any other file."""
144
+ try:
145
+ log = json.loads(Path(path).read_bytes().decode("utf-8-sig"))
146
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
147
+ raise ValueError(f"cannot read {path}: {exc}") from exc
148
+ applied = log.get("applied") if isinstance(log, dict) else None
149
+ if not isinstance(applied, list):
150
+ raise ValueError(f"{path} holds no applied[] list")
151
+ return applied
152
+
153
+
154
+ def _field_value(key: str, value: Any) -> str:
155
+ if value is None or value == "" or value == []:
156
+ return "latest" if key == "pin" else "none"
157
+ if isinstance(value, bool):
158
+ return "true" if value else "false"
159
+ if isinstance(value, list):
160
+ return ", ".join(str(v) for v in value)
161
+ if isinstance(value, dict):
162
+ return json.dumps(value, sort_keys=True)
163
+ return str(value)
164
+
165
+
166
+ def _brief_summary(skill_name: str, target: Optional[Dict[str, Any]]) -> str:
167
+ if not target:
168
+ return f"The campaign brief has no target entry for `{skill_name}`."
169
+ keys = [k for k in BRIEF_FIELDS if k in target] + [k for k in target if k not in BRIEF_FIELDS]
170
+ return "\n".join(f"- `{key}`: {_field_value(key, target[key])}" for key in keys)
171
+
172
+
173
+ def _bullet(text: str) -> str:
174
+ """One markdown bullet; a fact of several lines continues indented under it."""
175
+ first, *rest = text.strip().splitlines()
176
+ lines = [f"- {first}"] + [f" {line}" if line.strip() else "" for line in rest]
177
+ return "\n".join(lines)
178
+
179
+
180
+ def _facts_list(facts: List[str]) -> str:
181
+ bullets = [_bullet(fact) for fact in facts if fact.strip()]
182
+ return "\n".join(bullets) if bullets else NO_FACTS
183
+
184
+
185
+ def _fact_files(entry: str, pattern: str, root: str) -> List[Path]:
186
+ """The files a `file:` entry names: the path itself, else every file its glob matches.
187
+
188
+ `{project-root}` in the pattern becomes root, escaped where it joins a glob.
189
+ A path with no glob character that names no file raises FileNotFoundError,
190
+ so a mistyped path stops the kickoff instead of dropping its facts; a glob
191
+ that matches nothing adds no file.
192
+ """
193
+ path = Path(pattern.replace(PROJECT_ROOT, root))
194
+ if path.is_file():
195
+ return [path]
196
+ if not any(c in pattern for c in GLOB_CHARS):
197
+ raise FileNotFoundError(f"`{entry}` names no file ({path.as_posix()})")
198
+ matches = glob.glob(pattern.replace(PROJECT_ROOT, glob.escape(root)), recursive=True)
199
+ return [Path(p) for p in sorted(matches) if Path(p).is_file()]
200
+
201
+
202
+ def load_facts(entries: List[str], project_root: Optional[str] = None) -> List[str]:
203
+ """Turn persistent-facts entries into the facts the kickoff lists.
204
+
205
+ A `!` entry adds nothing and drops every earlier entry equal to what
206
+ follows the `!`, before any `file:` entry is read, so a dropped file is
207
+ never opened. A `file:` entry adds one fact per file it names, headed by
208
+ the file's path. Raises ValueError on an entry that keeps a placeholder
209
+ (as `{project-root}` does when project_root is None), FileNotFoundError
210
+ on a path that names no file, OSError on a file that cannot be read.
211
+ """
212
+ kept: List[str] = []
213
+ for entry in entries:
214
+ if entry.startswith(DROP_PREFIX):
215
+ dropped = entry[len(DROP_PREFIX):]
216
+ kept = [earlier for earlier in kept if earlier != dropped]
217
+ else:
218
+ kept.append(entry)
219
+ facts: List[str] = []
220
+ for entry in kept:
221
+ if not entry.startswith(FILE_PREFIX):
222
+ facts.append(entry)
223
+ continue
224
+ pattern = entry[len(FILE_PREFIX):].strip()
225
+ left = pattern if project_root is None else pattern.replace(PROJECT_ROOT, "")
226
+ placeholder = UNRESOLVED_RE.search(left)
227
+ if placeholder:
228
+ hint = "; pass --project-root" if placeholder.group(0) == PROJECT_ROOT else ""
229
+ raise ValueError(f"`{entry}` holds the unresolved placeholder `{placeholder.group(0)}`{hint}")
230
+ for path in _fact_files(entry, pattern, project_root or ""):
231
+ text = path.read_bytes().decode("utf-8", errors="replace")
232
+ facts.append(f"From `{path.as_posix()}`:\n\n{text}")
233
+ return facts
234
+
235
+
236
+ def read_directive(path: Optional[str]) -> Optional[str]:
237
+ """The directive file's text exactly as stored, or None when no file is there.
238
+
239
+ A byte that is not UTF-8 is kept as a surrogate, which _write_stdout turns
240
+ back into the same byte, so a directive in another encoding comes through
241
+ unchanged.
242
+ """
243
+ if not path or not Path(path).is_file():
244
+ return None
245
+ return Path(path).read_bytes().decode("utf-8", errors="surrogateescape")
71
246
 
72
247
 
73
248
  def render_kickoff(
@@ -75,7 +250,9 @@ def render_kickoff(
75
250
  brief: Dict[str, Any],
76
251
  skill_name: str,
77
252
  template: str,
78
- workarounds: Optional[List[str]] = None,
253
+ workarounds: Optional[List[Any]] = None,
254
+ directive: Optional[str] = None,
255
+ facts: Optional[List[str]] = None,
79
256
  ) -> str:
80
257
  campaign = state.get("campaign", {})
81
258
  skills = state.get("skills", [])
@@ -84,12 +261,13 @@ def render_kickoff(
84
261
  raise KeyError(f"Skill '{skill_name}' not found in state")
85
262
  skill = skill_map[skill_name]
86
263
 
87
- targets = {t["name"]: t for t in brief.get("targets", [])}
88
- repo_url = targets.get(skill_name, {}).get("repo_url", "")
264
+ targets = {t.get("name"): t for t in (brief.get("targets") or []) if isinstance(t, dict)}
265
+ target = targets.get(skill_name)
266
+ repo_url = (target or {}).get("repo_url", "")
89
267
 
90
268
  wa = workarounds if workarounds is not None else (skill.get("workarounds_applied", []) or [])
91
269
 
92
- mechanical = {
270
+ values = {
93
271
  "{{campaign_name}}": str(campaign.get("name", "")),
94
272
  "{{current_stage}}": str(campaign.get("current_stage", "")),
95
273
  "{{quality_gate_summary}}": _quality_gate_summary(campaign.get("quality_gate", {})),
@@ -100,59 +278,174 @@ def render_kickoff(
100
278
  "{{repo_url}}": repo_url,
101
279
  "{{workarounds_list}}": _workarounds_list(wa),
102
280
  "{{dependency_status_table}}": _dependency_status_table(skill, skill_map),
281
+ "{{brief_summary}}": _brief_summary(skill_name, target),
282
+ "{{persistent_facts}}": _facts_list(facts or []),
283
+ "{{directive_content}}": NO_DIRECTIVE if directive is None else directive,
103
284
  }
104
-
105
- out = template
106
- for key, value in mechanical.items():
107
- out = out.replace(key, value)
108
- return out
109
-
110
-
111
- def run(state_file: str, brief_file: str, skill: str, template_file: str, workarounds_json: Optional[str]) -> int:
285
+ # One pass: a filled-in value is never searched again.
286
+ return SLOT_RE.sub(lambda m: values.get(m.group(0), m.group(0)), template)
287
+
288
+
289
+ def _facts_entries(raw: str) -> List[Any]:
290
+ """The entries a --facts-json value holds: a JSON list, or the resolver's object."""
291
+ if not raw.strip():
292
+ raise ValueError("it is empty (with -, the command piped into stdin printed nothing)")
293
+ value = json.loads(raw)
294
+ if isinstance(value, dict) and set(value) <= {RESOLVER_KEY}:
295
+ value = value.get(RESOLVER_KEY, [])
296
+ if not isinstance(value, list):
297
+ raise ValueError(f"not a list, nor an object holding `{RESOLVER_KEY}`")
298
+ return value
299
+
300
+
301
+ def _read_stdin() -> str:
302
+ """stdin as UTF-8, the encoding the resolver writes; a byte-order mark is dropped."""
303
+ buffer = getattr(sys.stdin, "buffer", None)
304
+ if buffer is None:
305
+ return sys.stdin.read()
306
+ return buffer.read().decode("utf-8-sig")
307
+
308
+
309
+ def _write_stdout(text: str) -> None:
310
+ """Write UTF-8 bytes as they are, so no platform rewrites the directive's line endings.
311
+
312
+ A surrogate read_directive kept for a byte that is not UTF-8 becomes that byte again.
313
+ """
314
+ buffer = getattr(sys.stdout, "buffer", None)
315
+ if buffer is None:
316
+ sys.stdout.write(text)
317
+ return
318
+ sys.stdout.flush()
319
+ buffer.write(text.encode("utf-8", errors="surrogateescape"))
320
+ buffer.flush()
321
+
322
+
323
+ def run(
324
+ state_file: str,
325
+ brief_file: str,
326
+ skill: str,
327
+ template_file: str,
328
+ workarounds_file: Optional[str],
329
+ facts_json: Optional[str] = None,
330
+ directive_file: Optional[str] = None,
331
+ project_root: Optional[str] = None,
332
+ ) -> int:
333
+ texts: Dict[str, str] = {}
112
334
  for label, p in (("State", state_file), ("Brief", brief_file), ("Template", template_file)):
113
335
  if not Path(p).is_file():
114
336
  return _err(f"{label} file not found: {p}", f"{label.upper()}_NOT_FOUND")
337
+ try:
338
+ texts[label] = Path(p).read_text(encoding="utf-8")
339
+ except (OSError, UnicodeDecodeError) as exc:
340
+ return _err(f"{label} file unreadable: {p}: {exc}", f"{label.upper()}_UNREADABLE")
115
341
 
116
342
  try:
117
- state = yaml.safe_load(Path(state_file).read_text(encoding="utf-8"))
118
- brief = yaml.safe_load(Path(brief_file).read_text(encoding="utf-8"))
343
+ state = yaml.safe_load(texts["State"])
344
+ brief = yaml.safe_load(texts["Brief"])
119
345
  except yaml.YAMLError as exc:
120
346
  return _err(f"Failed to parse YAML: {exc}", "PARSE_ERROR")
121
- template = Path(template_file).read_text(encoding="utf-8")
347
+ if not isinstance(state, dict) or not isinstance(brief, dict):
348
+ return _err("State and brief must each be a YAML mapping", "PARSE_ERROR")
349
+
350
+ workarounds: Optional[List[Any]] = None
351
+ if workarounds_file:
352
+ try:
353
+ workarounds = read_workarounds(workarounds_file)
354
+ except ValueError as exc:
355
+ return _err(f"--workarounds-file: {exc}", "BAD_WORKAROUNDS")
122
356
 
123
- workarounds: Optional[List[str]] = None
124
- if workarounds_json:
357
+ entries: List[Any] = []
358
+ if facts_json:
125
359
  try:
126
- workarounds = json.loads(workarounds_json)
127
- if not isinstance(workarounds, list):
128
- raise ValueError("not a list")
360
+ entries = _facts_entries(_read_stdin() if facts_json == "-" else facts_json)
129
361
  except ValueError as exc:
130
- return _err(f"--workarounds must be a JSON list: {exc}", "BAD_WORKAROUNDS")
362
+ return _err(f"--facts-json must be a JSON list of strings: {exc}", "BAD_FACTS")
363
+ if not all(isinstance(e, str) for e in entries):
364
+ return _err("--facts-json must be a JSON list of strings: an entry is not a string", "BAD_FACTS")
365
+ if project_root is not None and (not project_root.strip() or not Path(project_root).is_dir()):
366
+ return _err(f"--project-root is not a directory: '{project_root}'", "BAD_FACTS")
367
+ try:
368
+ facts = load_facts(entries, project_root)
369
+ except ValueError as exc:
370
+ return _err(f"--facts-json: {exc}", "BAD_FACTS")
371
+ except FileNotFoundError as exc:
372
+ return _err(f"Persistent facts file not found: {exc}", "FACTS_FILE_NOT_FOUND")
373
+ except OSError as exc:
374
+ return _err(f"Persistent facts file unreadable: {exc}", "FACTS_UNREADABLE")
375
+
376
+ try:
377
+ directive = read_directive(directive_file)
378
+ except OSError as exc:
379
+ return _err(f"Directive file unreadable: {directive_file}: {exc}", "DIRECTIVE_UNREADABLE")
131
380
 
132
381
  try:
133
- rendered = render_kickoff(state, brief, skill, template, workarounds)
382
+ rendered = render_kickoff(state, brief, skill, texts["Template"], workarounds, directive, facts)
134
383
  except KeyError as exc:
135
384
  return _err(str(exc), "SKILL_NOT_FOUND")
136
385
 
137
- sys.stdout.write(rendered)
138
- if not rendered.endswith("\n"):
139
- sys.stdout.write("\n")
386
+ _write_stdout(rendered if rendered.endswith("\n") else rendered + "\n")
140
387
  return 0
141
388
 
142
389
 
143
- def main(argv: list[str] | None = None) -> int:
390
+ def build_parser() -> argparse.ArgumentParser:
144
391
  parser = argparse.ArgumentParser(
145
392
  prog="campaign-render-kickoff",
146
- description="Render the mechanical placeholders in the campaign kickoff template.",
393
+ description="Render the campaign kickoff template for one skill, every placeholder filled.",
147
394
  )
148
395
  parser.add_argument("--state-file", required=True)
149
396
  parser.add_argument("--brief-file", required=True)
150
397
  parser.add_argument("--skill", required=True, help="skill name (must exist in state)")
151
398
  parser.add_argument("--template", required=True, dest="template_file")
152
- parser.add_argument("--workarounds", dest="workarounds_json", help="JSON list of applied workarounds")
153
- args = parser.parse_args(argv)
154
- return run(args.state_file, args.brief_file, args.skill, args.template_file, args.workarounds_json)
399
+ parser.add_argument(
400
+ "--workarounds-file",
401
+ help="the pre-apply log (preapply-log.json) whose applied[] workarounds the kickoff lists",
402
+ )
403
+ parser.add_argument(
404
+ "--facts-json",
405
+ help="persistent facts as a JSON list of sentences, file:<path-or-glob> entries and !<entry> "
406
+ "entries (each drops the earlier entries equal to <entry>), or - to read them from stdin "
407
+ "(a list, or the resolver's output for --key workflow.persistent_facts)",
408
+ )
409
+ parser.add_argument(
410
+ "--project-root",
411
+ help="project root that replaces {project-root} in file: entries",
412
+ )
413
+ parser.add_argument(
414
+ "--directive-file",
415
+ help="directive file to inline byte for byte (absent or missing: 'No directive configured')",
416
+ )
417
+ return parser
418
+
419
+
420
+ def main(argv: list[str] | None = None) -> int:
421
+ args = build_parser().parse_args(argv)
422
+ return run(
423
+ args.state_file,
424
+ args.brief_file,
425
+ args.skill,
426
+ args.template_file,
427
+ args.workarounds_file,
428
+ args.facts_json,
429
+ args.directive_file,
430
+ args.project_root,
431
+ )
432
+
433
+
434
+ def _force_utf8(*streams) -> None:
435
+ """Reconfigure stdout and stderr to UTF-8, keeping each stream's error handler.
436
+
437
+ A Windows console pipes them as cp1252, which cannot print every character
438
+ a directive or a fact may hold.
439
+ """
440
+ for stream in streams:
441
+ if hasattr(stream, "reconfigure"):
442
+ errors = getattr(stream, "errors", None)
443
+ if errors is None:
444
+ stream.reconfigure(encoding="utf-8")
445
+ else:
446
+ stream.reconfigure(encoding="utf-8", errors=errors)
155
447
 
156
448
 
157
449
  if __name__ == "__main__":
450
+ _force_utf8(sys.stdout, sys.stderr)
158
451
  raise SystemExit(main())