@joshuafolkken/kit 1.1010.0 → 1.1050.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 (350) hide show
  1. package/.claude/settings.json +1 -5
  2. package/.claude/skills/dependency-update/SKILL.md +6 -3
  3. package/.claude/skills/epic-commands/SKILL.md +46 -17
  4. package/.claude/skills/verify-ui/SKILL.md +1 -1
  5. package/.claude/skills/workflow-commands/SKILL.md +10 -25
  6. package/.claude/skills/workflow-commands/background-commands.md +1 -1
  7. package/.claude/skills/workflow-commands/backlogrun-child.md +6 -4
  8. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  9. package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
  10. package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
  11. package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
  12. package/.claude/skills/workflow-commands/backlogrun.md +3 -12
  13. package/.claude/skills/workflow-commands/chain-rule.md +2 -2
  14. package/.claude/skills/workflow-commands/followup-reference.md +2 -2
  15. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
  16. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  17. package/.claude/skills/workflow-commands/halfrun.md +2 -1
  18. package/.claude/skills/workflow-commands/into-target.md +1 -1
  19. package/.claude/skills/workflow-commands/issue-comments.md +1 -1
  20. package/.claude/skills/workflow-commands/observation-filing.md +80 -126
  21. package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
  22. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  23. package/.claude/skills/workflow-commands/working-tree-hold.md +2 -2
  24. package/.codex/hooks.json +1 -5
  25. package/.github/workflows/auto-tag.yml +21 -0
  26. package/.github/workflows/publish.yml +9 -10
  27. package/.github/workflows/sonar-qube.yml +1 -1
  28. package/AGENTS.md +4 -25
  29. package/CLAUDE.md +24 -38
  30. package/GEMINI.md +4 -24
  31. package/README.md +22 -56
  32. package/dist/CLAUDE.md +24 -38
  33. package/dist/hooks/batch-guard.js +4 -4
  34. package/dist/hooks/{chunk-6PSTVOLH.js → chunk-5BK3K7V4.js} +114 -90
  35. package/dist/hooks/{chunk-PAXMLGSS.js → chunk-7VPDUBRN.js} +93 -175
  36. package/dist/hooks/{chunk-KPZN42RT.js → chunk-DMQN45FN.js} +3 -3
  37. package/dist/hooks/{chunk-WMIR5SNX.js → chunk-EJDX2TOB.js} +13 -3
  38. package/dist/hooks/{chunk-A2H5I2VD.js → chunk-GQ7KP45M.js} +2 -2
  39. package/dist/hooks/{chunk-4GDR2YTV.js → chunk-HJIISEAC.js} +4 -4
  40. package/dist/hooks/{chunk-AWTFKKFW.js → chunk-NAS4C7GP.js} +6 -33
  41. package/dist/hooks/{chunk-56LGMJJV.js → chunk-NJQOBE6L.js} +2 -2
  42. package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-RLDX5SOC.js} +1222 -941
  43. package/dist/hooks/{chunk-GBJUERYK.js → chunk-WQ4OSBSS.js} +10 -3
  44. package/dist/hooks/codex-hook-adapter.js +10 -10
  45. package/dist/hooks/duplicate-read-guard.js +5 -5
  46. package/dist/hooks/format-edited.js +15 -34
  47. package/dist/hooks/investigation-guard.js +5 -5
  48. package/dist/hooks/pretool-guard.js +23 -16
  49. package/dist/hooks/stop-guard.js +21 -200
  50. package/dist/josh.js +42 -125
  51. package/dist/version/index.js +466 -42
  52. package/eslint/base.js +2 -11
  53. package/package.json +36 -10
  54. package/prompts/coding-standards.md +15 -290
  55. package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
  56. package/prompts/collaboration-workflow/epic-audit.md +2 -53
  57. package/prompts/collaboration-workflow/epic-bundle.md +2 -106
  58. package/prompts/collaboration-workflow/file-edits.md +6 -53
  59. package/prompts/collaboration-workflow/issue-template.md +4 -4
  60. package/prompts/collaboration-workflow/operating-rules.md +18 -75
  61. package/prompts/collaboration-workflow/principles.md +74 -0
  62. package/prompts/collaboration-workflow/residency.md +55 -15
  63. package/prompts/collaboration-workflow/rule-delivery.md +5 -46
  64. package/prompts/collaboration-workflow/shell-body.md +7 -9
  65. package/prompts/collaboration-workflow/turn-batching.md +21 -37
  66. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  67. package/prompts/collaboration-workflow/wip-cap.md +15 -18
  68. package/prompts/collaboration-workflow.md +22 -28
  69. package/prompts/sonar-hotspot-handling.md +1 -1
  70. package/scripts/adopt/adopt-logic.ts +2 -2
  71. package/scripts/adopt/adopt-toolkits.ts +3 -1
  72. package/scripts/adopt/adopt.ts +2 -2
  73. package/scripts/agent/agent-event.ts +2 -9
  74. package/scripts/agent/agent-exit-record.ts +2 -9
  75. package/scripts/auto-ok/auto-ok-cli.ts +9 -9
  76. package/scripts/backlog/backlog-budget-cli.ts +2 -6
  77. package/scripts/backlog/backlog-drive-cli.ts +20 -10
  78. package/scripts/backlog/backlog-drive.ts +50 -16
  79. package/scripts/backlog/backlog-next.ts +24 -8
  80. package/scripts/backlog/backlog-offer-cli.ts +6 -5
  81. package/scripts/backlog/backlog-plan-cli.ts +5 -1
  82. package/scripts/backlog/backlog-plan.ts +1 -1
  83. package/scripts/backlog/backlog-pool.ts +31 -11
  84. package/scripts/backlog/backlog-rank.ts +106 -0
  85. package/scripts/backlog/backlog-waves.ts +42 -29
  86. package/scripts/build/build-bin.ts +2 -1
  87. package/scripts/build/build-config-merge.ts +2 -1
  88. package/scripts/build/build-hooks.ts +31 -6
  89. package/scripts/build/build-managed-marker.ts +2 -1
  90. package/scripts/build/build-self-sync-guard.ts +2 -1
  91. package/scripts/build/build-version.ts +2 -1
  92. package/scripts/cases/cases-logic.ts +2 -6
  93. package/scripts/ci/pr-classification.ts +13 -11
  94. package/scripts/cost-runtime/cost-transcript.ts +4 -11
  95. package/scripts/delegation/delegation-policy.ts +11 -0
  96. package/scripts/doctor/doctor-consumer.ts +5 -12
  97. package/scripts/doctor/doctor-ports.ts +2 -2
  98. package/scripts/document/document-byte-budget.ts +13 -21
  99. package/scripts/document/document-section.ts +2 -6
  100. package/scripts/document/entry-read-budget.ts +6 -5
  101. package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
  102. package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
  103. package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
  104. package/scripts/epic/epic-audit-cli.ts +4 -4
  105. package/scripts/epic/epic-audit-orphans.ts +2 -2
  106. package/scripts/epic/epic-audit-rationale.ts +1 -1
  107. package/scripts/epic/epic-audit.ts +1 -1
  108. package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
  109. package/scripts/epic/epic-bundle-cli.ts +1 -1
  110. package/scripts/epic/epic-bundle.ts +2 -2
  111. package/scripts/epic/epic-candidate-confirm.ts +2 -1
  112. package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
  113. package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
  114. package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
  115. package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
  116. package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
  117. package/scripts/epic/epic-cross-repo.ts +21 -38
  118. package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
  119. package/scripts/epic/epic-fetch.ts +3 -3
  120. package/scripts/epic/epic-graph.ts +3 -3
  121. package/scripts/epic/epic-index.ts +2 -2
  122. package/scripts/epic/epic-issue.ts +1 -1
  123. package/scripts/epic/epic-lane-offer.ts +11 -22
  124. package/scripts/epic/epic-next.ts +3 -3
  125. package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
  126. package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
  127. package/scripts/epic/epic-rank.ts +37 -0
  128. package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
  129. package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
  130. package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
  131. package/scripts/epic/epic-relation-recheck.ts +4 -3
  132. package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
  133. package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
  134. package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
  135. package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
  136. package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
  137. package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
  138. package/scripts/epic/epic-solo.ts +29 -28
  139. package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
  140. package/{scripts-ai → scripts/epic}/epic.ts +17 -16
  141. package/scripts/gh/fix-gh-packages.ts +2 -1
  142. package/scripts/gh/gh-spawn.ts +12 -10
  143. package/scripts/git/constants.ts +1 -1
  144. package/scripts/git/git-branch.ts +10 -1
  145. package/scripts/git/git-command.ts +2 -2
  146. package/scripts/git/git-error.ts +2 -5
  147. package/scripts/git/git-execa-error.ts +6 -1
  148. package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
  149. package/scripts/git/git-followup-flush.ts +74 -48
  150. package/scripts/git/git-followup-issue-close.ts +2 -2
  151. package/scripts/git/git-followup-label.ts +1 -1
  152. package/scripts/git/git-followup-pending.ts +1 -1
  153. package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
  154. package/scripts/git/git-gh-exec.ts +85 -26
  155. package/scripts/git/git-gh-issue-read.ts +2 -2
  156. package/scripts/git/git-gh-issue-rest.ts +2 -2
  157. package/scripts/git/git-gh-issue-write.ts +6 -6
  158. package/scripts/git/git-gh-pr-read.ts +0 -21
  159. package/scripts/git/git-gh-pr.ts +2 -23
  160. package/scripts/git/git-next-issues.ts +34 -5
  161. package/scripts/git/git-pr-followup-wrapup.ts +2 -6
  162. package/scripts/git/git-preflight.ts +25 -10
  163. package/scripts/git/git-push-transport.ts +12 -69
  164. package/scripts/git/git-spawn.ts +79 -3
  165. package/scripts/git/git-ssh-keepalive.ts +74 -0
  166. package/scripts/git/git-staging.ts +21 -9
  167. package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
  168. package/scripts/git/github-issue-url.ts +1 -1
  169. package/scripts/git/gone-branch.ts +3 -2
  170. package/scripts/git/issue-labels.ts +13 -1
  171. package/scripts/git/issue-rank.ts +89 -0
  172. package/scripts/git/issue-reference.ts +19 -0
  173. package/scripts/git/main-merge.ts +2 -1
  174. package/scripts/git/main-sync.ts +3 -2
  175. package/scripts/git/schemas.ts +1 -1
  176. package/scripts/git/stash-pop-args.ts +7 -13
  177. package/scripts/git/stash-pop-cli.ts +2 -1
  178. package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
  179. package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
  180. package/scripts/hooks/format-edited-cli.ts +15 -0
  181. package/scripts/hooks/format-edited-file.ts +2 -9
  182. package/scripts/hooks/format-rewrite-notice.ts +2 -8
  183. package/scripts/hooks/pretool-guard-cli.ts +21 -0
  184. package/scripts/hooks/pretool-guard.ts +14 -15
  185. package/scripts/init/init-ai-copy.ts +2 -5
  186. package/scripts/init/init.ts +8 -4
  187. package/scripts/init/start-exec.ts +7 -1
  188. package/scripts/init/start-plan.ts +18 -5
  189. package/scripts/init/start-setup-pr.ts +46 -19
  190. package/scripts/init/start-steps.ts +43 -10
  191. package/scripts/init/start.ts +42 -11
  192. package/scripts/issue/issue-cite.ts +19 -1
  193. package/scripts/issue/issue-comment-cli.ts +2 -1
  194. package/scripts/issue/issue-file-cli.ts +2 -1
  195. package/scripts/issue/issue-fold-cli.ts +9 -16
  196. package/scripts/issue/issue-scout-cli.ts +8 -13
  197. package/scripts/issue/measure-rerun-cli.ts +7 -6
  198. package/scripts/josh/josh-command-map.ts +0 -102
  199. package/scripts/josh/josh-commands-ai.ts +2 -2
  200. package/scripts/josh/josh-commands-backlog.ts +1 -1
  201. package/scripts/josh/josh-commands-development.ts +3 -3
  202. package/scripts/josh/josh-commands-project.ts +1 -1
  203. package/scripts/josh/josh-commands-workflow.ts +4 -4
  204. package/scripts/lane/lane-child-marker.ts +16 -2
  205. package/scripts/lane/lane-cli.ts +34 -15
  206. package/scripts/lane/lane-open.ts +3 -2
  207. package/scripts/lane/lane-registry.ts +3 -6
  208. package/scripts/lane/lane-report.ts +10 -4
  209. package/scripts/lib/cli-flags.ts +20 -2
  210. package/scripts/lib/error-message.ts +9 -0
  211. package/scripts/lib/read-file.ts +26 -2
  212. package/scripts/observations/observation-ledger-home.ts +98 -26
  213. package/scripts/observations/observation-ledger-migrate.ts +26 -21
  214. package/scripts/observations/observation-ledger-prepare.ts +10 -20
  215. package/scripts/observations/observation-ledger.ts +58 -25
  216. package/scripts/observations/observations-flush-cli.ts +6 -23
  217. package/scripts/observations/observations-flush-landing.ts +2 -1
  218. package/scripts/observations/observations-flush.ts +29 -20
  219. package/scripts/package/package-scout-cli.ts +8 -16
  220. package/scripts/ports/port-command.ts +2 -1
  221. package/scripts/propagate/propagate-plan.ts +185 -0
  222. package/scripts/propagate/propagate-publish.ts +6 -7
  223. package/scripts/propagate/propagate-run.ts +5 -1
  224. package/scripts/propagate/propagate-steps.ts +18 -2
  225. package/scripts/propagate/propagate-targets.ts +13 -2
  226. package/scripts/propagate/propagate.ts +63 -44
  227. package/scripts/refactor/import-graph.ts +2 -6
  228. package/scripts/registry-migration/migrate-logic.ts +1 -0
  229. package/scripts/registry-migration/migrate.ts +3 -2
  230. package/scripts/registry-migration/user-npmrc.ts +2 -2
  231. package/scripts/release/github-release.ts +1 -1
  232. package/scripts/release/release-cli.ts +2 -1
  233. package/scripts/repo/repo-setting.ts +10 -8
  234. package/scripts/review/review-attest.ts +44 -0
  235. package/scripts/review/review-brief-cli.ts +49 -2
  236. package/scripts/review/review-brief.ts +46 -1
  237. package/scripts/review/review-finding-ledger.ts +3 -3
  238. package/scripts/review/review-findings-cli.ts +4 -11
  239. package/scripts/review/review-record-cli.ts +14 -11
  240. package/scripts/review/review-record.ts +6 -16
  241. package/scripts/review/review-stamps.ts +7 -0
  242. package/scripts/rules/decision-oracle.ts +2 -2
  243. package/scripts/rules/implementation-cut.ts +1 -1
  244. package/scripts/rules/issue-fold.ts +1 -2
  245. package/scripts/rules/lane-carry-conflict.ts +1 -2
  246. package/scripts/rules/lane-park.ts +1 -2
  247. package/scripts/rules/pre-gate-cut.ts +2 -2
  248. package/scripts/rules/rule-body-guard.ts +2 -2
  249. package/scripts/rules/rule-value-cache.ts +99 -0
  250. package/scripts/rules/rule-value-cli.ts +10 -9
  251. package/scripts/rules/rule-value.ts +1 -1
  252. package/scripts/run/run-carry-args.ts +2 -6
  253. package/scripts/run/run-carry-cli.ts +5 -7
  254. package/scripts/run/run-cut-args.ts +7 -13
  255. package/scripts/run/run-ending-cli.ts +4 -7
  256. package/scripts/run/run-event-stream.ts +2 -5
  257. package/scripts/run/run-headless.ts +22 -6
  258. package/scripts/run/run-liveness-cli.ts +4 -7
  259. package/scripts/run/run-parent-cut-hook.ts +144 -0
  260. package/scripts/run/run-progress-args.ts +2 -6
  261. package/scripts/run/run-progress-cli.ts +2 -1
  262. package/scripts/run/run-progress-read.ts +2 -1
  263. package/scripts/run/run-progress.ts +21 -8
  264. package/scripts/run/run-ship-cli.ts +6 -74
  265. package/scripts/run/run-ship-preflight.ts +87 -0
  266. package/scripts/run/run-ship-review-steps.ts +5 -26
  267. package/scripts/run/run-ship-review.ts +9 -0
  268. package/scripts/run/run-ship-scoped.ts +44 -0
  269. package/scripts/run/run-ship-stage.ts +5 -1
  270. package/scripts/run/run-ship-steps.ts +105 -0
  271. package/scripts/run/run-ship.ts +2 -0
  272. package/scripts/run/run-tail-cli.ts +3 -3
  273. package/scripts/run/run-tidy-stashes.ts +7 -14
  274. package/scripts/run/run-wake-cli.ts +4 -8
  275. package/scripts/run/run-wake-describe.ts +19 -1
  276. package/scripts/run/run-wake-driver.ts +15 -2
  277. package/scripts/run/run-wake-session.ts +14 -4
  278. package/scripts/run/run-watcher-hook.ts +1 -1
  279. package/scripts/safe-chain/project-config.ts +3 -6
  280. package/scripts/security/security-audit-provision.ts +6 -7
  281. package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
  282. package/scripts/sync/directory-copy-guard.ts +2 -1
  283. package/scripts/sync/sync-ai-files.ts +3 -3
  284. package/scripts/sync/template-source-logic.ts +3 -4
  285. package/scripts/test/e2e-retry-check.ts +3 -6
  286. package/scripts/time-runtime/time-bundle-call.ts +2 -2
  287. package/scripts/ui/ui-routes-cli.ts +3 -6
  288. package/scripts/version/bump-version.ts +2 -1
  289. package/scripts/version/npm-registry.ts +97 -0
  290. package/scripts/version/version-commands.ts +5 -4
  291. package/scripts/version/version-remote.ts +38 -33
  292. package/scripts/version/version-targets.ts +3 -3
  293. package/.claude/skills/workflow-commands/rule-residency.md +0 -267
  294. package/evals/scenarios/consult-not-execute.json +0 -20
  295. package/evals/scenarios/no-implicit-workflow.json +0 -26
  296. package/evals/scenarios/overrides-both-locations.json +0 -29
  297. package/evals/scenarios/upstream-interrupt.json +0 -23
  298. package/evals/scenarios/workflow-skill-loaded.json +0 -16
  299. package/prompts/agent-lint-protocol.json +0 -79
  300. package/prompts/agent-rules.md +0 -63
  301. package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
  302. package/prompts/collaboration-workflow/distributed-docs.md +0 -7
  303. package/prompts/collaboration-workflow/durable-rules.md +0 -24
  304. package/prompts/collaboration-workflow/latest-first.md +0 -14
  305. package/prompts/collaboration-workflow/no-clones.md +0 -11
  306. package/prompts/collaboration-workflow/simplicity-first.md +0 -9
  307. package/prompts/collaboration-workflow/single-source-rules.md +0 -13
  308. package/scripts/build/shim-shell.ts +0 -25
  309. package/scripts/claude/skill-meta.ts +0 -37
  310. package/scripts/document/document-scan.ts +0 -108
  311. package/scripts/document/generate-catalog.ts +0 -69
  312. package/scripts/document/retired-phrases.ts +0 -143
  313. package/scripts/document/review-agent-type.ts +0 -46
  314. package/scripts/document/single-source.ts +0 -73
  315. package/scripts/git/git-fixture-workspace.ts +0 -88
  316. package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
  317. package/scripts/hooks/prompt-hooks.ts +0 -38
  318. package/scripts/init/hook-command-bootstrap.ts +0 -83
  319. package/scripts/issue/issue-logic.ts +0 -50
  320. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
  321. package/scripts/josh/josh-script-reader.ts +0 -11
  322. package/scripts/package/production-dependency-graph.ts +0 -68
  323. package/scripts/retrospective/retrospective-cli.ts +0 -97
  324. package/scripts/retrospective/retrospective.ts +0 -127
  325. package/scripts/rules/delivered-rules-harness.ts +0 -72
  326. package/scripts/rules/top-level-scripts-guard.ts +0 -47
  327. package/scripts/run/run-carry-flush.ts +0 -58
  328. package/scripts/run/run-transcript.golden.txt +0 -57
  329. package/scripts/run/run-wake-work.ts +0 -108
  330. package/scripts/sync/dependabot-pin-logic.ts +0 -135
  331. package/scripts/test/classify-isolation.ts +0 -149
  332. package/scripts/test/http-fault-injection.ts +0 -79
  333. package/scripts/test/josh-harness-environment.ts +0 -260
  334. package/scripts/test/josh-harness.ts +0 -96
  335. package/scripts/test/pilot-files.ts +0 -452
  336. package/scripts/test/pilot-measure.ts +0 -78
  337. package/scripts/test/test-network-guard.ts +0 -453
  338. package/scripts/test/test-repository-guard.ts +0 -317
  339. package/scripts/test/test-state-guard.ts +0 -85
  340. package/scripts/test/test-stdout-guard.ts +0 -54
  341. package/scripts/test/test-telegram-guard.ts +0 -90
  342. package/scripts/test/unit-guard-environment.ts +0 -12
  343. package/scripts/test/unit-projects.ts +0 -139
  344. package/scripts/test/vitest-include-globs.ts +0 -12
  345. package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
  346. package/scripts-ai/completion-gate.ts +0 -13
  347. package/scripts-ai/environment-loader.ts +0 -9
  348. /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
  349. /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
  350. /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
@@ -0,0 +1,105 @@
1
+ import { josh_command, type JoshResult } from '#scripts/josh/josh-run'
2
+ import { run_ship } from './run-ship'
3
+ import { run_ship_preflight } from './run-ship-preflight'
4
+ import { run_ship_review_steps } from './run-ship-review-steps'
5
+ import { run_ship_scoped } from './run-ship-scoped'
6
+ import { run_ship_stage, type ShipState, type Stage } from './run-ship-stage'
7
+
8
+ // The stages `josh ship` runs, in the order a change ships (joshuafolkken/kit#2398), kept apart from the
9
+ // chaining CLI so the order is read in one place.
10
+
11
+ const should_forward_stderr = true
12
+
13
+ interface ShipArguments {
14
+ title: string
15
+ number: string
16
+ notify: ReadonlyArray<string>
17
+ body: ReadonlyArray<string>
18
+ body_path: string | undefined
19
+ cites: ReadonlyArray<string>
20
+ is_review: boolean
21
+ is_detach: boolean
22
+ }
23
+
24
+ interface Step {
25
+ stage: Stage
26
+ header: string
27
+ run: (args: ShipArguments, state: ShipState) => Promise<JoshResult>
28
+ }
29
+
30
+ const { STAGE } = run_ship_stage
31
+
32
+ async function josh(argv: ReadonlyArray<string>): Promise<JoshResult> {
33
+ return await josh_command.josh_run(argv, should_forward_stderr)
34
+ }
35
+
36
+ // joshuafolkken/kit#2946: the pull request's preconditions and the scoped pair are asked first, so a
37
+ // stop they would cause lands before the review and the gate rather than after them.
38
+ const PREFLIGHT_STEP: Step = {
39
+ stage: STAGE.PREFLIGHT,
40
+ header: run_ship.PREFLIGHT_HEADER,
41
+ run: async (args) =>
42
+ await run_ship_preflight.stage({ title: args.title, body_path: args.body_path }),
43
+ }
44
+
45
+ // The gate before the commit, the commit/push/PR before the merge. The gate meets the scoped pair
46
+ // first: a round-1 reviewer may have edited the tree since the preflight, and `josh gate` refuses a tree
47
+ // with no green record (joshuafolkken/kit#2946). The commit step carries the `--skip-*` flags a resumed
48
+ // ship needs, so an existing commit or push is never made twice.
49
+ const COMMIT_STEPS: ReadonlyArray<Step> = [
50
+ {
51
+ stage: STAGE.GATE,
52
+ header: run_ship.GATE_HEADER,
53
+ run: async () =>
54
+ await run_ship_scoped.run_phases([
55
+ run_ship_scoped.scoped_pair,
56
+ async () => await josh(['gate']),
57
+ ]),
58
+ },
59
+ {
60
+ stage: STAGE.COMMIT,
61
+ header: run_ship.COMMIT_HEADER,
62
+ run: async (args, state) =>
63
+ await josh(['git', '-y', ...run_ship_stage.commit_flags(state), ...args.body, args.title]),
64
+ },
65
+ ]
66
+
67
+ // The merge before the report bookkeeping.
68
+ const MERGE_STEPS: ReadonlyArray<Step> = [
69
+ {
70
+ stage: STAGE.FOLLOWUP,
71
+ header: run_ship.FOLLOWUP_HEADER,
72
+ run: async (args) => await josh(['followup', args.title, ...args.notify]),
73
+ },
74
+ {
75
+ stage: STAGE.REPORT,
76
+ header: run_ship.REPORT_HEADER,
77
+ run: async (args) => await josh(['run:tail', args.number, ...args.cites]),
78
+ },
79
+ ]
80
+
81
+ // `--review` (joshuafolkken/kit#2427) puts the supervised round-1 review in front of the gate: it
82
+ // launches the gate itself, so the gate stage that follows reuses that tree's green record. It also
83
+ // puts the round-2 pass between the commit and the followup (joshuafolkken/kit#2489), so the PR opens
84
+ // between the rounds and round 2 runs beside CI — a no-op when round 1 left no fix delta.
85
+ const REVIEW_STEP: Step = {
86
+ stage: STAGE.REVIEW,
87
+ header: run_ship.REVIEW_HEADER,
88
+ run: async (args) => await run_ship_review_steps.review_stage(args.number),
89
+ }
90
+ const ROUND_TWO_STEP: Step = {
91
+ stage: STAGE.ROUND_TWO,
92
+ header: run_ship.ROUND_TWO_HEADER,
93
+ run: async (args) => await run_ship_review_steps.round_two_stage(args.number),
94
+ }
95
+
96
+ function steps(args: ShipArguments): ReadonlyArray<Step> {
97
+ if (!args.is_review) return [PREFLIGHT_STEP, ...COMMIT_STEPS, ...MERGE_STEPS]
98
+
99
+ return [PREFLIGHT_STEP, REVIEW_STEP, ...COMMIT_STEPS, ROUND_TWO_STEP, ...MERGE_STEPS]
100
+ }
101
+
102
+ const run_ship_steps = { steps }
103
+
104
+ export type { ShipArguments, Step }
105
+ export { run_ship_steps }
@@ -9,6 +9,7 @@
9
9
  // `run:tail`, ship stops at the first failed step: a red gate must never reach the commit, so the
10
10
  // composite ends at the failure, and the report names the step that failed so the run reads only it.
11
11
 
12
+ const PREFLIGHT_HEADER = '=== preflight ==='
12
13
  const REVIEW_HEADER = '=== review ==='
13
14
  const GATE_HEADER = '=== gate ==='
14
15
  const COMMIT_HEADER = '=== commit/push/PR ==='
@@ -63,6 +64,7 @@ const run_ship = {
63
64
  COMMIT_HEADER,
64
65
  FOLLOWUP_HEADER,
65
66
  GATE_HEADER,
67
+ PREFLIGHT_HEADER,
66
68
  REPORT_HEADER,
67
69
  REVIEW_HEADER,
68
70
  ROUND_TWO_HEADER,
@@ -17,9 +17,9 @@ import { run_tail, type TailSection } from './run-tail'
17
17
  //
18
18
  // **A dispatched lane child skips the ledger step** (joshuafolkken/kit#2492). A flush opens a
19
19
  // ledger-only pull request and waits out its whole CI before it merges — minutes per issue, spent on a
20
- // file only the retrospective and the measurements read. The line stays appended in the primary
21
- // checkout, and the `backlogrun` parent flushes once at its end (`run:carry --end`, `run-carry-flush.ts`).
22
- // A single run outside a lane keeps the step: its one end is already the run's end.
20
+ // file only the retrospective and the measurements read — and a lane has nothing for it: its lines
21
+ // merged with its own pull request (joshuafolkken/kit#2919). A single run outside a lane keeps the
22
+ // step for a line written on the default branch outside any issue's run.
23
23
 
24
24
  const ARGV_OFFSET = 2
25
25
  const SUCCESS_EXIT_CODE = 0
@@ -1,4 +1,3 @@
1
- import { readFile } from 'node:fs/promises'
2
1
  import { git_stash, type StashEntry } from '#scripts/git/git-stash'
3
2
  import { stash_orphans } from '#scripts/git/stash-orphans'
4
3
  import { stash_sweep_lock } from '#scripts/git/stash-sweep-lock'
@@ -18,16 +17,8 @@ const READ_CONCURRENCY = 8
18
17
  const GONE_REASON = 'already dropped by another run'
19
18
  const BUSY_REASON = 'another run held the stash sweep lock'
20
19
 
21
- async function read_ledger(ledger_path: string): Promise<string> {
22
- try {
23
- return await readFile(ledger_path, 'utf8')
24
- } catch {
25
- return ''
26
- }
27
- }
28
-
29
- // An entry cut before joshuafolkken/kit#2724 holds its lines at the ledger's old path, so the lines of
30
- // every ledger path it touches are carried to the current ledger.
20
+ // An entry cut before joshuafolkken/kit#2919 holds its lines at an old single-file path, so the lines
21
+ // of every ledger path it touches are carried to the current ledger.
31
22
  async function added_ledger_lines(
32
23
  hash: string,
33
24
  ledger_paths: ReadonlyArray<string>,
@@ -43,16 +34,18 @@ async function added_ledger_lines(
43
34
  return added
44
35
  }
45
36
 
46
- // How many ledger lines went into the primary checkout's ledger before the drop.
37
+ // How many ledger lines went into the running work tree's ledger before the drop. The duplicate check
38
+ // reads every issue's file, and the lines land in the file this tree writes (joshuafolkken/kit#2919),
39
+ // so the run's own commit takes them to the default branch.
47
40
  async function carry_ledger(hash: string): Promise<number> {
48
41
  const changed = await git_stash.changed_paths(hash)
49
42
  const ledger_paths = changed.filter((file_path) => observation_ledger.is_ledger_path(file_path))
50
43
 
51
44
  if (ledger_paths.length === 0) return 0
52
45
 
53
- const ledger_path = observation_ledger_home.ledger_path()
46
+ const ledger_path = await observation_ledger_home.writer_path(new Date())
54
47
  const added = await added_ledger_lines(hash, ledger_paths)
55
- const carried = run_tidy.ledger_carry(added, await read_ledger(ledger_path))
48
+ const carried = run_tidy.ledger_carry(added, (await observation_ledger_home.read()) ?? '')
56
49
 
57
50
  await observation_ledger_home.append(ledger_path, carried)
58
51
 
@@ -2,9 +2,9 @@
2
2
  import { randomUUID } from 'node:crypto'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
- import { parseArgs } from 'node:util'
6
5
  import type { AgentProfile } from '#scripts/agent/agent-role-profile'
7
6
  import { telegram_notify } from '#scripts/git/telegram-notify'
7
+ import { cli_flags } from '#scripts/lib/cli-flags'
8
8
  import { run_carry, type CarryRead } from './run-carry'
9
9
  import { run_event_stream } from './run-event-stream'
10
10
  import { run_headless } from './run-headless'
@@ -120,11 +120,7 @@ function note_to_stderr(note: string): void {
120
120
  }
121
121
 
122
122
  function read_arguments(argv: ReadonlyArray<string>): ParsedValues | undefined {
123
- try {
124
- return parseArgs({ args: [...argv], options: OPTIONS, strict: true }).values
125
- } catch {
126
- return undefined
127
- }
123
+ return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values
128
124
  }
129
125
 
130
126
  function text_of(value: string | boolean | undefined): string | undefined {
@@ -294,7 +290,7 @@ function start(context: WakeContext, interval: string | undefined): number {
294
290
  const existing = run_wake.read_wake(context.wake_target)
295
291
 
296
292
  if (existing !== undefined && run_wake.is_supervisor_live(existing)) {
297
- console.error(run_wake_describe.describe_wake(existing, context))
293
+ console.error(run_wake_describe.describe_wake_cited(existing, context))
298
294
 
299
295
  return report(RUNNING_VERDICT)
300
296
  }
@@ -346,7 +342,7 @@ function list(context: WakeContext): number {
346
342
 
347
343
  if (wake === undefined) return report(NONE_VERDICT)
348
344
 
349
- console.error(run_wake_describe.describe_wake(wake, context))
345
+ console.error(run_wake_describe.describe_wake_cited(wake, context))
350
346
 
351
347
  return report(run_wake.is_supervisor_live(wake) ? SUPERVISING_VERDICT : STALE_VERDICT)
352
348
  }
@@ -1,4 +1,6 @@
1
1
  import { agent_role_profile } from '#scripts/agent/agent-role-profile'
2
+ import { gh_spawn } from '#scripts/gh/gh-spawn'
3
+ import { issue_citation } from '#scripts/rules/issue-citation'
2
4
  import { run_carry, type CarryRead } from './run-carry'
3
5
  import { run_event_stream } from './run-event-stream'
4
6
  import { run_liveness } from './run-liveness'
@@ -9,6 +11,7 @@ import { run_wake, type RunWake } from './run-wake'
9
11
  // record reads back to a person lives here, one cohesive block.
10
12
 
11
13
  const UNKNOWN_CUTS = 'an unreadable number of'
14
+ const REPO_LOOKUP_TIMEOUT_MS = 10_000
12
15
  const STOP_COMMAND = 'pnpm josh run:wake --stop'
13
16
 
14
17
  // The three paths every verb needs: the carry record it reads, its own record, and the work tree a
@@ -91,7 +94,22 @@ function describe_wake(wake: RunWake, context: WakeContext): string {
91
94
  .join('\n')
92
95
  }
93
96
 
94
- const run_wake_describe = { STOP_COMMAND, describe_wake }
97
+ // The description as a person reads it, every issue on it a number-link (joshuafolkken/kit#2943). The
98
+ // invocation and the newest event both name issues (`backlogrun #1 #2`, `#1904 merged`), and a report
99
+ // that copies them bare is what the Stop guard sends back. The repository is asked here, bounded, rather
100
+ // than in the context every verb resolves: only the two describing verbs print it, and a lookup that
101
+ // does not answer leaves the description as it was rather than withholding it.
102
+ function cite_description(description: string, repo: string | undefined): string {
103
+ return repo === undefined ? description : issue_citation.linkify(description, repo)
104
+ }
105
+
106
+ function describe_wake_cited(wake: RunWake, context: WakeContext): string {
107
+ const repo = gh_spawn.get_repo_name_with_owner_within(REPO_LOOKUP_TIMEOUT_MS)
108
+
109
+ return cite_description(describe_wake(wake, context), repo)
110
+ }
111
+
112
+ const run_wake_describe = { STOP_COMMAND, cite_description, describe_wake, describe_wake_cited }
95
113
 
96
114
  export type { WakeContext }
97
115
  export { run_wake_describe }
@@ -6,6 +6,8 @@ import type { DriveResult } from './run-wake-loop'
6
6
  const SUCCESS = 0
7
7
  const FIRST_LINE = 0
8
8
  const RESUME_PREFIX = 'resume: '
9
+ const DETAILS_TAIL_LENGTH = 2048
10
+ const TRUNCATION_MARK = '... '
9
11
 
10
12
  function driver_args(invocation: string): ReadonlyArray<string> | undefined {
11
13
  const rebuilt = run_invocation.rebuild(invocation)
@@ -24,13 +26,24 @@ function is_final_stop(verdict: string | undefined, read: CarryRead): boolean {
24
26
  return verdict?.startsWith('stop') === true && read.kind === 'none'
25
27
  }
26
28
 
29
+ // **The details are the driver's whole stderr, so only their tail is handed on, and it goes last**
30
+ // (joshuafolkken/kit#2931). The stderr carries every command the driver ran since it started, which
31
+ // after a long run is tens of kilobytes — far past what `is_safe_value` lets into an argv element. Its
32
+ // tail is where the reason for stopping is; placing it last means the cap `wake_argv` applies to the
33
+ // whole prompt can only ever cut into the details, never the verdict, the resume line or the epic note.
34
+ function details_tail(details: string): string {
35
+ return details.length > DETAILS_TAIL_LENGTH
36
+ ? `${TRUNCATION_MARK}${details.slice(-DETAILS_TAIL_LENGTH)}`
37
+ : details
38
+ }
39
+
27
40
  function handoff_material(verdict: string, resume: string, details: string): string {
28
- const context = details === '' ? '' : `\nDetails: ${details}`
41
+ const context = details === '' ? '' : `\nDetails: ${details_tail(details)}`
29
42
  const epic = verdict.startsWith('epic #')
30
43
  ? '\nThe named item is an epic. Follow backlogrun-steps.md named epic procedure; do not launch the epic root as a fullrun child. After all children merge or park, record the root with pnpm josh run:carry --done <epic-number> --owner "$PPID" before continuing to the next named item.'
31
44
  : ''
32
45
 
33
- return `Driver result: ${verdict}\n${resume}${context}${epic}`
46
+ return `Driver result: ${verdict}\n${resume}${epic}${context}`
34
47
  }
35
48
 
36
49
  function driver_result(out: string, read: CarryRead, details = ''): DriveResult {
@@ -37,6 +37,8 @@ const WAKE_FLAGS = claude_agent_argv.AGENT_FLAGS
37
37
  const LOOP_FLAG = '--loop'
38
38
  const INTERVAL_FLAG = '--interval'
39
39
  const SUPERVISOR_SESSION = 'run-wake-supervisor'
40
+ // eslint-disable-next-line no-control-regex -- matching control characters is the point
41
+ const CONTROL_CHARACTERS = /[\u{0}-\u{1F}\u{7F}]/gu
40
42
 
41
43
  function scheduler_profile(): AgentProfile | undefined {
42
44
  const resolved = agent_role_profile.resolve(agent_role_profile.SCHEDULER)
@@ -96,6 +98,17 @@ function resolved_argv(invocation: string, cwd?: string): AgentArgvResult {
96
98
  : agent_argv.resolve_in(invocation, agent_role_profile.SCHEDULER, cwd)
97
99
  }
98
100
 
101
+ // **Handoff material is made safe to launch rather than refused** (joshuafolkken/kit#2931). It is the
102
+ // driver's own output, not the record, so it is flattened — a newline becomes a separator, any other
103
+ // control character a space — and the prompt is cut at the cap `is_safe_value` enforces. The driver puts
104
+ // its verdict and resume line first, so the cut only ever shortens the details. The check itself is
105
+ // untouched: the argv still passes `is_safe_argv` before anything is spawned.
106
+ function handoff_prompt(matched: string, material: string): string {
107
+ const flattened = material.replaceAll('\n', ' | ').replaceAll(CONTROL_CHARACTERS, ' ')
108
+
109
+ return `${matched} | Driver handoff: ${flattened}`.slice(0, agent_role_profile.MAX_VALUE_LENGTH)
110
+ }
111
+
99
112
  function wake_argv(
100
113
  invocation: string,
101
114
  profile?: AgentProfile,
@@ -104,10 +117,7 @@ function wake_argv(
104
117
  ): AgentArgvResult | undefined {
105
118
  const matched = safe_invocation(invocation)
106
119
  if (matched === undefined) return undefined
107
- const prompt =
108
- typeof session === 'object'
109
- ? `${matched} | Driver handoff: ${session.material.replaceAll('\n', ' | ')}`
110
- : matched
120
+ const prompt = typeof session === 'object' ? handoff_prompt(matched, session.material) : matched
111
121
  const session_id = typeof session === 'object' ? session.id : session
112
122
 
113
123
  return profile === undefined
@@ -81,6 +81,6 @@ async function watcher_hook_reason(
81
81
  return await stale_reason(transcript, now_ms)
82
82
  }
83
83
 
84
- const run_watcher_hook = { SWITCH_ENV_KEY, STAMP_PREFIX, watcher_hook_reason }
84
+ const run_watcher_hook = { SWITCH_ENV_KEY, STAMP_PREFIX, transcript_of, watcher_hook_reason }
85
85
 
86
86
  export { run_watcher_hook }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
+ import { file_reader } from '#scripts/lib/read-file'
3
4
  import { release_age } from '#scripts/version/release-age'
4
5
  import { yaml_document } from '#scripts/yaml/yaml-document'
5
6
  import { dump } from 'js-yaml'
@@ -126,17 +127,13 @@ function merge_project_config(existing: string, workspace: string, npmrc = ''):
126
127
  return replace_safe_chain_block(existing, merged)
127
128
  }
128
129
 
129
- function read_optional(file_path: string): string {
130
- return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
131
- }
132
-
133
130
  function sync_project_config(root: string): boolean {
134
131
  const workspace_path = path.join(root, WORKSPACE_FILE)
135
132
  if (!existsSync(workspace_path)) return false
136
133
  const config_path = path.join(root, PROJECT_CONFIG_FILE)
137
- const existing = read_optional(config_path)
134
+ const existing = file_reader.read_file_or_empty(config_path)
138
135
  const workspace = readFileSync(workspace_path, 'utf8')
139
- const npmrc = read_optional(path.join(root, NPMRC_FILE))
136
+ const npmrc = file_reader.read_file_or_empty(path.join(root, NPMRC_FILE))
140
137
  const merged = merge_project_config(existing, workspace, npmrc)
141
138
  if (merged === existing) return false
142
139
  writeFileSync(config_path, merged)
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
6
6
  import { stamp_file } from '#scripts/josh/stamp-file'
7
+ import { error_text } from '#scripts/lib/error-message'
7
8
  import { security_audit } from './security-audit'
8
9
  import { security_audit_logic } from './security-audit-logic'
9
10
  import { security_audit_provision_logic, type ScannerAsset } from './security-audit-provision-logic'
@@ -14,10 +15,6 @@ const FORCE_FLAG = '--force'
14
15
  const PATH_LOCATION = 'on PATH'
15
16
  const { RETRY_INTERVAL_MS } = security_audit_provision_logic
16
17
 
17
- function describe_error(error: unknown): string {
18
- return error instanceof Error ? error.message : String(error)
19
- }
20
-
21
18
  // `content` absent is the failure, and `reason` says which one. A 404 — what a bumped version whose
22
19
  // asset was renamed looks like, and the one failure that never fixes itself — would otherwise read
23
20
  // exactly like a transient 503, and a rejected request like neither.
@@ -41,7 +38,7 @@ async function download(url: string, timeout_ms: number): Promise<DownloadOutcom
41
38
 
42
39
  return { content: Buffer.from(await response.arrayBuffer()), reason: '' }
43
40
  } catch (error) {
44
- return { reason: describe_error(error) }
41
+ return { reason: error_text.message_of(error) }
45
42
  }
46
43
  }
47
44
 
@@ -128,7 +125,7 @@ async function attempt(
128
125
  } catch (error) {
129
126
  return record_failure(
130
127
  target_path,
131
- security_audit_provision_logic.format_provision_error(describe_error(error)),
128
+ security_audit_provision_logic.format_provision_error(error_text.message_of(error)),
132
129
  )
133
130
  }
134
131
  }
@@ -175,7 +172,9 @@ async function main(): Promise<void> {
175
172
  try {
176
173
  console.info(await report(PROJECT_ROOT, process.platform, process.arch, is_forced))
177
174
  } catch (error) {
178
- console.warn(security_audit_provision_logic.format_provision_error(describe_error(error)))
175
+ console.warn(
176
+ security_audit_provision_logic.format_provision_error(error_text.message_of(error)),
177
+ )
179
178
  }
180
179
  }
181
180
 
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { git_command } from '#scripts/git/git-command'
6
+ import { error_text } from '#scripts/lib/error-message'
6
7
  import { managed_config_scope } from '#scripts/sync/managed-config-scope'
7
8
  import { z } from 'zod'
8
9
  import {
@@ -69,7 +70,7 @@ async function fetch_hotspots(url: string): Promise<HotspotFetch> {
69
70
 
70
71
  return { hotspots: parsed.hotspots ?? [] }
71
72
  } catch (error) {
72
- return { error: error instanceof Error ? error.message : String(error) }
73
+ return { error: error_text.message_of(error) }
73
74
  }
74
75
  }
75
76
 
@@ -1,6 +1,7 @@
1
1
  import { cpSync, lstatSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { transform_copied_content } from '#scripts/init/init-copy-content'
4
+ import { error_text } from '#scripts/lib/error-message'
4
5
  import { file_content } from './file-content'
5
6
 
6
7
  // `josh init` and `josh sync` both copy a distributed directory with `cpSync`, and both have to
@@ -143,7 +144,7 @@ function copy_directory(source_path: string, destination_path: string): boolean
143
144
  // throw here would end `josh init` or `josh sync` in the middle of their work. The message is
144
145
  // returned for the caller to report, the same way a blocker is.
145
146
  function failure_message(prefix: string, error: unknown): string {
146
- return `${prefix} (${error instanceof Error ? error.message : String(error)})`
147
+ return `${prefix} (${error_text.message_of(error)})`
147
148
  }
148
149
 
149
150
  // The two failures are reported apart because they leave the destination in opposite states, and
@@ -5,6 +5,7 @@ import { transform_copied_content } from '#scripts/init/init-copy-content'
5
5
  import { init_logic } from '#scripts/init/init-logic'
6
6
  import { PACKAGE_DIR, PROJECT_ROOT } from '#scripts/init/init-paths'
7
7
  import type { ProjectShape } from '#scripts/init/project-profile'
8
+ import { file_reader } from '#scripts/lib/read-file'
8
9
  import { copy_directory_failure, directory_copy_blocker } from './directory-copy-guard'
9
10
  import { file_content } from './file-content'
10
11
  import { REMOVED_SKILL_MANIFEST } from './removed-skill-manifest'
@@ -70,8 +71,7 @@ function sync_workspace_yaml(
70
71
  is_force = false,
71
72
  ): boolean {
72
73
  const template = readFileSync(template_path, 'utf8')
73
- const existing =
74
- !is_force && existsSync(destination_path) ? readFileSync(destination_path, 'utf8') : ''
74
+ const existing = is_force ? '' : file_reader.read_file_or_empty(destination_path)
75
75
  const merged = init_logic.merge_workspace_yaml(existing, template)
76
76
 
77
77
  return file_content.write_text_if_changed(destination_path, merged)
@@ -155,7 +155,7 @@ function ensured_claude_md(existing: string | undefined, shape?: ProjectShape):
155
155
  // without ever disturbing those additions — so this ignores --force, which would otherwise mean
156
156
  // discarding a consumer's content.
157
157
  function sync_claude_md(destination_path: string, shape?: ProjectShape): void {
158
- const existing = existsSync(destination_path) ? readFileSync(destination_path, 'utf8') : undefined
158
+ const existing = file_reader.read_optional(destination_path)
159
159
  const ensured = ensured_claude_md(existing, shape)
160
160
 
161
161
  if (ensured === existing) {
@@ -1,6 +1,7 @@
1
1
  import { createHash } from 'node:crypto'
2
- import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { copyFileSync, readFileSync, writeFileSync } from 'node:fs'
3
3
  import { package_path } from '#scripts/init/init-paths'
4
+ import { file_reader } from '#scripts/lib/read-file'
4
5
  import { z } from 'zod'
5
6
 
6
7
  interface TemplateSourcePair {
@@ -39,9 +40,7 @@ function read_file(relative_path: string): string {
39
40
  }
40
41
 
41
42
  function read_optional_file(relative_path: string): string {
42
- const full_path = package_path(relative_path)
43
-
44
- return existsSync(full_path) ? readFileSync(full_path, 'utf8') : ''
43
+ return file_reader.read_file_or_empty(package_path(relative_path))
45
44
  }
46
45
 
47
46
  function hash_text(text: string): string {
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env tsx
2
- import { appendFileSync, readdirSync, readFileSync, statSync } from 'node:fs'
2
+ import { appendFileSync, readdirSync, statSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { file_reader } from '#scripts/lib/read-file'
5
6
 
6
7
  // Whether the preview server process died during a failed E2E attempt.
7
8
  //
@@ -59,11 +60,7 @@ function has_crash_signature(log_text: string): boolean {
59
60
  }
60
61
 
61
62
  function read_file_safe(file_path: string): string {
62
- try {
63
- return readFileSync(file_path, 'utf8')
64
- } catch {
65
- return ''
66
- }
63
+ return file_reader.read_if_readable(file_path) ?? ''
67
64
  }
68
65
 
69
66
  // Every file under the directory, because wrangler names the debug log after the run and a job that
@@ -336,8 +336,8 @@ function josh_subcommand(command: string): string | undefined {
336
336
  }
337
337
 
338
338
  // Whether a shell line is one of the read-only `josh` bookkeeping commands, and nothing else. A chained
339
- // line falls through. An alias is resolved to its canonical name first — a run may type `josh ist` for
340
- // `issue:state` — so the allow-list holds canonical names alone. Everything not on the list falls
339
+ // line falls through. An alias is resolved to its canonical name first, so the allow-list holds
340
+ // canonical names alone. Everything not on the list falls
341
341
  // through to the read/mutation test, which is what keeps `josh gate` / `followup` / `git`
342
342
  // non-bundleable (joshuafolkken/kit#1875).
343
343
  function is_read_josh(command: string): boolean {
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env tsx
2
- import { readdirSync, readFileSync } from 'node:fs'
2
+ import { readdirSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { changed_paths } from '#scripts/git/changed-paths'
6
6
  import { git_command } from '#scripts/git/git-command'
7
+ import { file_reader } from '#scripts/lib/read-file'
7
8
  import { ui_routes } from './ui-routes'
8
9
 
9
10
  // `josh ui:routes [--staged]` — list the screenshot-target routes the current change touches
@@ -59,11 +60,7 @@ function route_files(root: string): Array<RouteFile> {
59
60
  }
60
61
 
61
62
  function read_text(file: string): string {
62
- try {
63
- return readFileSync(file, 'utf8')
64
- } catch {
65
- return ''
66
- }
63
+ return file_reader.read_if_readable(file) ?? ''
67
64
  }
68
65
 
69
66
  // A route file imports the component when its source names the component's file — the import
@@ -2,6 +2,7 @@
2
2
  import { readFileSync, writeFileSync } from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
+ import { error_text } from '#scripts/lib/error-message'
5
6
  import { json_object_schema, package_with_version_schema } from '#scripts/lib/schemas'
6
7
  import semver from 'semver'
7
8
 
@@ -61,7 +62,7 @@ function main(): void {
61
62
  try {
62
63
  bump_version(bump_type as BumpType)
63
64
  } catch (error) {
64
- console.error(error instanceof Error ? error.message : String(error))
65
+ console.error(error_text.message_of(error))
65
66
  process.exit(1)
66
67
  }
67
68
  }