@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
@@ -19,6 +19,11 @@ const RUN_SOLO_LABEL = 'run:solo'
19
19
  // (joshuafolkken/kit#2779). Without it an issue lacking `run:solo` could mean "judged parallel" or
20
20
  // "never judged", and `backlog:next` withholds the second (`epic-triage.ts`).
21
21
  const RUN_LANE_LABEL = 'run:lane'
22
+ // Puts an issue at the head of the backlog's ranking (joshuafolkken/kit#2928). A person or a run may
23
+ // apply it — a run only where it can cite a deadline or a stated urgency, or a policy a person wrote
24
+ // (`backlogrun-steps.md`) — and **only a person removes it**: a run that could clear it could undo
25
+ // the person's ordering. Read by `issue-rank.ts`, the one place the ranking keys are decided.
26
+ const PRIORITY_HIGH_LABEL = 'priority:high'
22
27
  // Opts one issue outside any epic into unattended execution (joshuafolkken/kit#906). **Only a
23
28
  // person applies it.** `epicrun #<E>` approves the merges inside `#<E>`; this label is the only way
24
29
  // a person extends that approval past the epic's edge, so a label an AI could apply to itself would
@@ -212,6 +217,11 @@ const WORKFLOW_LABELS: ReadonlyArray<LabelDefinition> = [
212
217
  color: '0e8a16',
213
218
  description: 'Judged safe to run beside others in a backlogrun',
214
219
  },
220
+ {
221
+ name: PRIORITY_HIGH_LABEL,
222
+ color: 'e99695',
223
+ description: 'Offered first in a backlogrun; only a person removes it',
224
+ },
215
225
  ...FILING_ROUTE_LABELS,
216
226
  ...DEPTH_LABELS,
217
227
  ]
@@ -282,7 +292,7 @@ function label_name_of(labels: ReadonlyArray<string>, wanted: string): string |
282
292
 
283
293
  // The same comparison for a caller holding label *names* rather than listing rows. `EpicChild.labels`
284
294
  // is an array of strings (`scripts/epic/epic-graph.ts`), which is the one shape `has_any_label` cannot
285
- // take — so `epic-classify.ts` and `git-epic-validate.ts` each grew a raw case-sensitive
295
+ // take — so `epic-classify.ts` and `epic-validate.ts` each grew a raw case-sensitive
286
296
  // `Array.includes` instead, and an `Epic`-cased label walked past both. Kept here beside the rule it
287
297
  // implements rather than at either call site, for the reason the comment above gives.
288
298
  //
@@ -322,6 +332,7 @@ const ALL_LABELS: ReadonlySet<string> = new Set([
322
332
  AUTO_OK_LABEL,
323
333
  NEEDS_HUMAN_REVIEW_LABEL,
324
334
  ALREADY_DONE_LABEL,
335
+ PRIORITY_HIGH_LABEL,
325
336
  INTERRUPT_ROUTE_LABEL,
326
337
  REVIEW_CAP_ROUTE_LABEL,
327
338
  REVIEW_ROUND2_SKIPPED_LABEL,
@@ -364,6 +375,7 @@ export {
364
375
  NEEDS_HUMAN_REVIEW_LABEL,
365
376
  NOT_DIRECTLY_RUNNABLE_LABELS,
366
377
  OTHER_CHANGE_LABEL,
378
+ PRIORITY_HIGH_LABEL,
367
379
  RELEASE_CLASSIFICATION_LABELS,
368
380
  RELEASE_CLASSIFICATION_NAMES,
369
381
  REPOSITORY_LABELS,
@@ -0,0 +1,89 @@
1
+ import {
2
+ BUG_LABEL,
3
+ has_label_name,
4
+ INTERRUPT_ROUTE_LABEL,
5
+ PRIORITY_HIGH_LABEL,
6
+ RUN_SOLO_LABEL,
7
+ } from './issue-labels'
8
+
9
+ // The keys that decide which open issue is offered first (joshuafolkken/kit#2928), defined once for
10
+ // every ranking that asks: the `🗒 Next issues` display, the `auto-ok` pickup and `backlog:next`.
11
+ //
12
+ // In order: `priority:high`, then a defect on the verification path (`bug` with `run:solo`, or
13
+ // `route:interrupt`), then how many other open issues wait on it. Below those, the caller's own order
14
+ // stands — the sort is stable, so recency and the defect-rate adjustment survive as the last key.
15
+ //
16
+ // **`run:solo` alone is not a key.** It means "runs alone", not "runs first": tidying that only touches
17
+ // the verification path carries it too, and a low-priority one is not owed a place ahead of the queue.
18
+ // Only a defect on that path is, because `wip-cap.md` holds a batch until it has merged.
19
+
20
+ interface RankRow {
21
+ labels: ReadonlyArray<string>
22
+ dependents: number
23
+ }
24
+
25
+ // One open issue as its key and the keys of the issues it is blocked by. The caller names the key
26
+ // format, so a ranking over listing rows and one over graph children each count in their own terms.
27
+ interface DependencyRow {
28
+ key: string
29
+ blockers: ReadonlyArray<string>
30
+ }
31
+
32
+ const NO_DEPENDENTS = 0
33
+
34
+ function flag(is_set: boolean): number {
35
+ return is_set ? 1 : 0
36
+ }
37
+
38
+ function is_priority(labels: ReadonlyArray<string>): boolean {
39
+ return has_label_name(labels, PRIORITY_HIGH_LABEL)
40
+ }
41
+
42
+ function is_verification_defect(labels: ReadonlyArray<string>): boolean {
43
+ if (has_label_name(labels, INTERRUPT_ROUTE_LABEL)) return true
44
+
45
+ return has_label_name(labels, BUG_LABEL) && has_label_name(labels, RUN_SOLO_LABEL)
46
+ }
47
+
48
+ // Negated so that the larger value sorts first under an ascending comparison.
49
+ function keys_of(row: RankRow): ReadonlyArray<number> {
50
+ return [
51
+ -flag(is_priority(row.labels)),
52
+ -flag(is_verification_defect(row.labels)),
53
+ -row.dependents,
54
+ ]
55
+ }
56
+
57
+ function compare(first: RankRow, second: RankRow): number {
58
+ const second_keys = keys_of(second)
59
+ const differences = keys_of(first).map((key, index) => key - (second_keys[index] ?? 0))
60
+
61
+ return differences.find((difference) => difference !== 0) ?? 0
62
+ }
63
+
64
+ // How many of the rows wait on each key. A blocker outside the rows counts nothing, because only an
65
+ // open issue in the same pool can be held back by it.
66
+ function count_dependents(rows: ReadonlyArray<DependencyRow>): ReadonlyMap<string, number> {
67
+ const counts = new Map<string, number>()
68
+
69
+ for (const row of rows) {
70
+ const blockers = new Set(row.blockers)
71
+
72
+ for (const blocker of blockers) counts.set(blocker, (counts.get(blocker) ?? NO_DEPENDENTS) + 1)
73
+ }
74
+
75
+ return counts
76
+ }
77
+
78
+ // The items in rank order, ties kept in the order they arrived.
79
+ function rank<T>(items: ReadonlyArray<T>, row_of: (item: T) => RankRow): Array<T> {
80
+ return items
81
+ .map((item) => ({ item, row: row_of(item) }))
82
+ .toSorted((first, second) => compare(first.row, second.row))
83
+ .map((entry) => entry.item)
84
+ }
85
+
86
+ const issue_rank = { compare, count_dependents, is_priority, is_verification_defect, rank }
87
+
88
+ export { issue_rank }
89
+ export type { DependencyRow, RankRow }
@@ -0,0 +1,19 @@
1
+ // An issue named from outside any one graph — a task-list row, a citation in prose, a blocker
2
+ // relation, a child that could not be read. Repository **and** number, because a number alone cannot
3
+ // identify one: issue numbers are unique per repository, so the same `#40` names two different issues
4
+ // depending on who wrote it (joshuafolkken/kit#1014).
5
+ //
6
+ // It lives in the git layer because the `blocked-by` relations are read here and carry a repository
7
+ // of their own (joshuafolkken/kit#1126); the epic domain builds on this layer, never the other way
8
+ // round (joshuafolkken/kit#2904). `epic-reference` re-exports it, so every epic importer is unchanged.
9
+ //
10
+ // `state` is present only on a reference read from a `blocked-by` relation whose response carried it
11
+ // (joshuafolkken/kit#1943). The classifier needs it for a blocker no named graph tracks, where there is
12
+ // no child record to read the state from; every other reference leaves it out.
13
+ interface IssueReference {
14
+ repo: string
15
+ number: number
16
+ state?: 'OPEN' | 'CLOSED'
17
+ }
18
+
19
+ export type { IssueReference }
@@ -2,6 +2,7 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { extract_issue_number } from '#scripts/hooks/check-commit-message'
4
4
  import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
5
+ import { error_text } from '#scripts/lib/error-message'
5
6
  import { git_command } from './git-command'
6
7
  import { main_merge_guard } from './main-merge-guard'
7
8
 
@@ -103,7 +104,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
103
104
  try {
104
105
  return await dispatch(argv)
105
106
  } catch (error) {
106
- console.error(error instanceof Error ? error.message : String(error))
107
+ console.error(error_text.message_of(error))
107
108
 
108
109
  return FAILURE_EXIT_CODE
109
110
  }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { composite_arguments, USAGE_ERROR_EXIT_CODE } from '#scripts/josh/josh-composite-arguments'
4
+ import { error_text } from '#scripts/lib/error-message'
4
5
  import { git_command } from './git-command'
5
6
  import { gone_branch, type PruneResult } from './gone-branch'
6
7
 
@@ -65,7 +66,7 @@ async function prune_quietly(default_branch: string): Promise<void> {
65
66
  try {
66
67
  report_prune(await gone_branch.prune(default_branch))
67
68
  } catch (error) {
68
- console.error(`branch prune skipped: ${error instanceof Error ? error.message : String(error)}`)
69
+ console.error(`branch prune skipped: ${error_text.message_of(error)}`)
69
70
  }
70
71
  }
71
72
 
@@ -104,7 +105,7 @@ async function run(argv: ReadonlyArray<string>): Promise<number> {
104
105
  try {
105
106
  return await dispatch(argv)
106
107
  } catch (error) {
107
- console.error(error instanceof Error ? error.message : String(error))
108
+ console.error(error_text.message_of(error))
108
109
 
109
110
  return FAILURE_EXIT_CODE
110
111
  }
@@ -111,7 +111,7 @@ const rest_comment_schema = z.looseObject({
111
111
  // Labels come back as objects, so the name is picked out here rather than at every call site.
112
112
  const issue_label_schema = z.object({ name: z.string() })
113
113
 
114
- // `state` is optional because most readers ignore it — only `git-epic-add.ts` consults it, to refuse
114
+ // `state` is optional because most readers ignore it — only `epic-add.ts` consults it, to refuse
115
115
  // adding a child to a closed epic (joshuafolkken/kit#2337). A reader that never asks the state field
116
116
  // still parses cleanly.
117
117
  const epic_subject_schema = z.object({
@@ -1,4 +1,4 @@
1
- import { parseArgs } from 'node:util'
1
+ import { cli_flags } from '#scripts/lib/cli-flags'
2
2
 
3
3
  // `josh stash:pop` turns its `argv` into a single request: the message of the stash to pop. Parsing
4
4
  // lives here, acting on the stack lives in `stash-pop-cli.ts`, and a malformed command line is
@@ -21,18 +21,12 @@ interface Request {
21
21
  }
22
22
 
23
23
  function read_arguments(argv: ReadonlyArray<string>): ParsedValues | undefined {
24
- try {
25
- const parsed = parseArgs({
26
- args: [...argv],
27
- options: OPTIONS,
28
- strict: true,
29
- allowPositionals: true,
30
- })
31
-
32
- return { values: parsed.values, positionals: parsed.positionals }
33
- } catch {
34
- return undefined
35
- }
24
+ return cli_flags.parse_or_undefined({
25
+ args: [...argv],
26
+ options: OPTIONS,
27
+ strict: true,
28
+ allowPositionals: true,
29
+ })
36
30
  }
37
31
 
38
32
  // The message is the sole positional and must be a non-empty string: an empty message matches every
@@ -1,4 +1,5 @@
1
1
  import { fileURLToPath } from 'node:url'
2
+ import { error_text } from '#scripts/lib/error-message'
2
3
  import { git_stash } from './git-stash'
3
4
  import { stash_pop_args, type Request } from './stash-pop-args'
4
5
 
@@ -66,7 +67,7 @@ async function answer(request: Request): Promise<number> {
66
67
  }
67
68
 
68
69
  function report_unknown(error: unknown): number {
69
- console.error(error instanceof Error ? error.message : String(error))
70
+ console.error(error_text.message_of(error))
70
71
 
71
72
  return FAILURE_EXIT_CODE
72
73
  }
@@ -1,5 +1,5 @@
1
- import type { TelegramSendInput, TelegramTaskType } from '../scripts/git/telegram-notify'
2
- import { cli_body } from '../scripts/josh/cli-body'
1
+ import { cli_body } from '#scripts/josh/cli-body'
2
+ import type { TelegramSendInput, TelegramTaskType } from './telegram-notify'
3
3
 
4
4
  /* eslint-disable @typescript-eslint/naming-convention */
5
5
  interface CliValues {
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { parseArgs } from 'node:util'
4
- import { git_error } from '../scripts/git/git-error'
5
- import { git_gh_issue_read } from '../scripts/git/git-gh-issue-read'
6
- import { git_gh_repo } from '../scripts/git/git-gh-repo'
7
- import { github_issue_url, type IssueUrlTarget } from '../scripts/git/github-issue-url'
8
- import { telegram_notify } from '../scripts/git/telegram-notify'
9
- import { load_optional_environment } from './environment-loader'
4
+ import { josh_environment_file } from '#scripts/josh/josh-environment-file'
5
+ import { git_error } from './git-error'
6
+ import { git_gh_issue_read } from './git-gh-issue-read'
7
+ import { git_gh_repo } from './git-gh-repo'
8
+ import { github_issue_url, type IssueUrlTarget } from './github-issue-url'
9
+ import { telegram_notify } from './telegram-notify'
10
10
  import { telegram_test_logic, type CliValues, type ResolvedContext } from './telegram-test-logic'
11
11
 
12
12
  const REPO_NAME_SEPARATOR = '/'
@@ -97,7 +97,7 @@ async function resolve_context(values: CliValues): Promise<ResolvedContext> {
97
97
  }
98
98
 
99
99
  async function main(): Promise<void> {
100
- load_optional_environment()
100
+ josh_environment_file.load_environment_file()
101
101
  const values = parse_cli_arguments()
102
102
  const context = await resolve_context(values)
103
103
  const input = telegram_test_logic.build_input({ values, context })
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env tsx
2
+ import { text } from 'node:stream/consumers'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { report_no_payload, run_hook } from './format-edited-file'
5
+
6
+ // The `PostToolUse` hook's entry, kept apart from `format-edited-file.ts` for the reason
7
+ // `pretool-guard-cli.ts` gives (joshuafolkken/kit#2922): `codex-hook-adapter.ts` imports that module,
8
+ // so a self-invoke left in it lands in a shared chunk and the bundled hook never runs.
9
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
10
+ if (process.stdin.isTTY) {
11
+ report_no_payload()
12
+ } else {
13
+ await run_hook(await text(process.stdin))
14
+ }
15
+ }
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { existsSync } from 'node:fs'
3
3
  import path from 'node:path'
4
- import { text } from 'node:stream/consumers'
5
4
  import { fileURLToPath } from 'node:url'
6
5
  import { resolve_local_bin, resolve_package_bin } from '#scripts/build/local-bin'
7
6
  import { ESLINT_EDIT_CACHE_FLAGS } from '#scripts/josh/josh-command-types'
@@ -482,14 +481,6 @@ async function run_hook(payload: string): Promise<void> {
482
481
  if (context !== undefined) process.stdout.write(`${build_envelope(context)}\n`)
483
482
  }
484
483
 
485
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
486
- if (process.stdin.isTTY) {
487
- report_no_payload()
488
- } else {
489
- await run_hook(await text(process.stdin))
490
- }
491
- }
492
-
493
484
  export {
494
485
  build_envelope,
495
486
  compose_context,
@@ -501,7 +492,9 @@ export {
501
492
  plan_commands,
502
493
  plan_daemon_restart,
503
494
  relative_to_root,
495
+ report_no_payload,
504
496
  resolve_invocations,
497
+ run_hook,
505
498
  select_invocation,
506
499
  ESLINT_DAEMON,
507
500
  MAX_DIAGNOSTIC_CHARS,
@@ -1,4 +1,4 @@
1
- import { readFileSync } from 'node:fs'
1
+ import { file_reader } from '#scripts/lib/read-file'
2
2
 
3
3
  // The rewrite half of the edit hook (joshuafolkken/kit#2314). The format hook runs prettier and
4
4
  // `eslint --fix` in place after every edit, so a file the model just wrote can be rewritten out from
@@ -85,13 +85,7 @@ function build(before: string, after: string): string | undefined {
85
85
  // handed nothing for, or one gone by the time it reads, compares as an empty string rather than
86
86
  // throwing — the hook runs after the write already succeeded and must never turn it into a failure.
87
87
  function read_text(file_path: string | undefined): string {
88
- if (file_path === undefined) return ''
89
-
90
- try {
91
- return readFileSync(file_path, 'utf8')
92
- } catch {
93
- return ''
94
- }
88
+ return file_path === undefined ? '' : (file_reader.read_if_readable(file_path) ?? '')
95
89
  }
96
90
 
97
91
  const rewrite_notice = { build, read_text }
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env tsx
2
+ import { text } from 'node:stream/consumers'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { hook_decision } from '#scripts/josh/hook-decision'
5
+ import { pretool_outcome_async } from './pretool-guard'
6
+
7
+ // The `PreToolUse` hook's entry, kept apart from the composition in `pretool-guard.ts`
8
+ // (joshuafolkken/kit#2922). The hook bundles are built with `splitting: true`, which moves a module
9
+ // imported by two entries into a shared chunk — and `codex-hook-adapter.ts` imports `pretool-guard.ts`.
10
+ // Its self-invoke went into that chunk, whose `import.meta.url` never equals the launched
11
+ // `dist/hooks/pretool-guard.js`, so the bundled hook ran no guard at all and every refusal failed open.
12
+ // An entry nothing imports keeps the self-invoke in the file that is actually launched.
13
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
14
+ if (process.stdin.isTTY) hook_decision.report_no_payload('pretool:guard')
15
+ else {
16
+ const raw_payload = await text(process.stdin)
17
+
18
+ hook_decision.load_environment_file()
19
+ hook_decision.emit_outcome(await pretool_outcome_async(raw_payload))
20
+ }
21
+ }
@@ -1,10 +1,8 @@
1
- #!/usr/bin/env tsx
2
- import { text } from 'node:stream/consumers'
3
- import { fileURLToPath } from 'node:url'
4
1
  import { duplicate_read_outcome } from '#scripts/delegation/duplicate-read-guard'
5
2
  import { investigation_refusal } from '#scripts/delegation/investigation-guard'
6
- import { hook_decision, type GuardOutcome } from '#scripts/josh/hook-decision'
3
+ import type { GuardOutcome } from '#scripts/josh/hook-decision'
7
4
  import { delivered_rules } from '#scripts/rules/delivered-rules'
5
+ import { run_parent_cut_hook } from '#scripts/run/run-parent-cut-hook'
8
6
  import { run_watcher_hook } from '#scripts/run/run-watcher-hook'
9
7
  import { batch_outcome } from './batch-guard'
10
8
 
@@ -71,26 +69,27 @@ function pretool_outcome(raw_payload: string): GuardOutcome {
71
69
  // as `combine_outcomes` orders them, and the watcher's refusal fills a clear verdict rather than
72
70
  // overriding one. It fires once per run itself (`run-watcher-hook.ts`), so a stale watcher refuses the
73
71
  // first call but not the `pnpm josh run:progress --wait` that fixes it.
72
+ //
73
+ // **The parent hand-off guard is the second asynchronous rule, asked after the watcher's**
74
+ // (joshuafolkken/kit#2947). It reads the carry record to tell the `backlogrun` parent apart, so it sits
75
+ // beside the watcher rather than among the synchronous rows; a stale watcher is the cheaper fix and is
76
+ // surfaced first.
77
+ async function async_reason(raw_payload: string): Promise<string | undefined> {
78
+ const watcher = await run_watcher_hook.watcher_hook_reason(raw_payload)
79
+
80
+ return watcher ?? (await run_parent_cut_hook.parent_cut_reason(raw_payload))
81
+ }
82
+
74
83
  async function pretool_outcome_async(raw_payload: string): Promise<GuardOutcome> {
75
84
  const base = pretool_outcome(raw_payload)
76
85
 
77
86
  if (!is_clear(base)) return base
78
87
 
79
- const reason = await run_watcher_hook.watcher_hook_reason(raw_payload)
88
+ const reason = await async_reason(raw_payload)
80
89
 
81
90
  return reason === undefined ? base : { reason, notice: undefined, fault: undefined }
82
91
  }
83
92
 
84
93
  const pretool_guard = { combine_outcomes, pretool_outcome, pretool_outcome_async }
85
94
 
86
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
87
- if (process.stdin.isTTY) hook_decision.report_no_payload('pretool:guard')
88
- else {
89
- const raw_payload = await text(process.stdin)
90
-
91
- hook_decision.load_environment_file()
92
- hook_decision.emit_outcome(await pretool_outcome_async(raw_payload))
93
- }
94
- }
95
-
96
95
  export { pretool_guard, pretool_outcome, pretool_outcome_async }
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { is_workflow_destination } from '#scripts/claude/workflow-destination'
4
4
  import { gh_spawn } from '#scripts/gh/gh-spawn'
5
+ import { file_reader } from '#scripts/lib/read-file'
5
6
  import { managed_marker_logic } from '#scripts/managed-marker/managed-marker-logic'
6
7
  import {
7
8
  classify_path,
@@ -238,11 +239,7 @@ function did_skip_ai_directory_copy(directory_name: string): boolean {
238
239
  // moved onto the basic path — only that kit-written line, never the consumer's additions.
239
240
  // An unreadable file is left as it is and reported as skipped, as it was before the migration.
240
241
  function read_if_readable(destination_path: string): string | undefined {
241
- try {
242
- return readFileSync(destination_path, 'utf8')
243
- } catch {
244
- return undefined
245
- }
242
+ return file_reader.read_if_readable(destination_path)
246
243
  }
247
244
 
248
245
  function report_existing_claude_md(destination_path: string): void {
@@ -4,6 +4,7 @@ import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  import { resolve_local_bin } from '#scripts/build/local-bin'
6
6
  import { doctor_io } from '#scripts/doctor/doctor-io'
7
+ import { error_text } from '#scripts/lib/error-message'
7
8
  import { package_version_schema, with_package_manager_schema } from '#scripts/lib/schemas'
8
9
  import { auto_merge_setting } from '#scripts/repo/auto-merge-setting'
9
10
  import { project_config } from '#scripts/safe-chain/project-config'
@@ -45,8 +46,11 @@ function show_sample(action: FileAction): void {
45
46
  console.info(action.create().replaceAll(/^/gmu, () => SAMPLE_INDENT))
46
47
  }
47
48
 
48
- function report_unchanged(action: FileAction): void {
49
- if (action.should_show_sample_when_unchanged === true) show_sample(action)
49
+ // A file that already holds the sample needs nothing added, so only one that differs gets it shown.
50
+ function report_unchanged(action: FileAction, existing: string): void {
51
+ const is_sample_missing = existing !== action.create()
52
+
53
+ if (is_sample_missing && action.should_show_sample_when_unchanged === true) show_sample(action)
50
54
  else console.info(` ✔ unchanged ${action.dest}`)
51
55
  }
52
56
 
@@ -59,7 +63,7 @@ function merge_existing_file(
59
63
  const merged = merge_function(existing)
60
64
 
61
65
  if (merged === existing) {
62
- report_unchanged(action)
66
+ report_unchanged(action, existing)
63
67
 
64
68
  return
65
69
  }
@@ -375,7 +379,7 @@ function run_cli(args: ReadonlyArray<string>): void {
375
379
  try {
376
380
  if (main(args)) print_start_hint()
377
381
  } catch (error) {
378
- console.error(`\n✖ ${error instanceof Error ? error.message : String(error)}\n`)
382
+ console.error(`\n✖ ${error_text.message_of(error)}\n`)
379
383
  process.exitCode = 1
380
384
  }
381
385
  }
@@ -23,6 +23,12 @@ function run(command: string, args: ReadonlyArray<string>, root: string): void {
23
23
  execaSync(command, args, { ...git_gh_exec.direct_environment(), cwd: root, stdio: 'inherit' })
24
24
  }
25
25
 
26
- const start_exec = { succeeds, read_output, run }
26
+ // A caller's own initialize command stands in for the in-process `josh init`, so it keeps the
27
+ // environment that setup would have had — its install goes through a scanner's proxy as usual.
28
+ function run_local(command: string, args: ReadonlyArray<string>, root: string): void {
29
+ execaSync(command, args, { cwd: root, stdio: 'inherit' })
30
+ }
31
+
32
+ const start_exec = { succeeds, read_output, run, run_local }
27
33
 
28
34
  export { start_exec }
@@ -8,6 +8,7 @@ interface StartOptions {
8
8
  is_yes: boolean
9
9
  is_github: boolean
10
10
  visibility: Visibility
11
+ init_command: string | undefined
11
12
  }
12
13
 
13
14
  type StartStep = 'git_init' | 'initialize' | 'commit' | 'repository' | 'labels' | 'setup_pr'
@@ -137,20 +138,31 @@ function github_consent(
137
138
  }
138
139
  }
139
140
 
141
+ // A run whose initialize step was handed to a caller's command names that command wherever the step
142
+ // is printed, so the plan, the progress and a failure all say what actually runs (#2872).
143
+ function step_labels(init_command: string | undefined): Readonly<Record<StartStep, string>> {
144
+ if (init_command === undefined) return STEP_LABELS
145
+
146
+ return { ...STEP_LABELS, initialize: `Initialize with ${init_command}` }
147
+ }
148
+
140
149
  // What a failed run prints, so the user can tell how far it got before deciding how to resume.
141
150
  function progress_report(
142
151
  failed: StartStep,
143
152
  completed: ReadonlyArray<StartStep>,
144
153
  cause: string,
154
+ labels: Readonly<Record<StartStep, string>> = STEP_LABELS,
145
155
  ): string {
146
- const done =
147
- completed.length === 0 ? 'nothing' : completed.map((step) => STEP_LABELS[step]).join('; ')
156
+ const done = completed.length === 0 ? 'nothing' : completed.map((step) => labels[step]).join('; ')
148
157
 
149
- return `josh start stopped at: ${STEP_LABELS[failed]}\nCompleted: ${done}\nCause: ${cause}`
158
+ return `josh start stopped at: ${labels[failed]}\nCompleted: ${done}\nCause: ${cause}`
150
159
  }
151
160
 
152
- function plan_summary(steps: ReadonlyArray<StartStep>): string {
153
- const lines = steps.map((step, index) => ` ${String(index + 1)}. ${STEP_LABELS[step]}`)
161
+ function plan_summary(
162
+ steps: ReadonlyArray<StartStep>,
163
+ labels: Readonly<Record<StartStep, string>> = STEP_LABELS,
164
+ ): string {
165
+ const lines = steps.map((step, index) => ` ${String(index + 1)}. ${labels[step]}`)
154
166
 
155
167
  return ['josh start will:', ...lines].join('\n')
156
168
  }
@@ -161,6 +173,7 @@ const start_plan = {
161
173
  github_consent,
162
174
  progress_report,
163
175
  plan_summary,
176
+ step_labels,
164
177
  STEP_LABELS,
165
178
  }
166
179
  export { start_plan }