@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
@@ -1,9 +1,9 @@
1
- import { git_epic_body } from './git-epic-body'
2
- import { git_epic_promote } from './git-epic-promote'
3
- import { git_epic_relations } from './git-epic-relations'
4
- import { git_epic_validate, type EpicSubject } from './git-epic-validate'
5
- import { git_gh_command } from './git-gh-command'
6
- import { EPIC_LABEL } from './issue-labels'
1
+ import { git_gh_command } from '#scripts/git/git-gh-command'
2
+ import { EPIC_LABEL } from '#scripts/git/issue-labels'
3
+ import { epic_body } from './epic-body'
4
+ import { epic_promote } from './epic-promote'
5
+ import { epic_relations } from './epic-relations'
6
+ import { epic_validate, type EpicSubject } from './epic-validate'
7
7
 
8
8
  const EPIC_LABEL_COLOR = '#5319e7'
9
9
  const EPIC_LABEL_DESCRIPTION = 'Tracks a batch of child issues from one split'
@@ -20,18 +20,18 @@ interface CreateEpicInput {
20
20
 
21
21
  // The relations the declared order implies, applied through the shared module `--add` also uses so
22
22
  // the two cannot drift. A failure is reported and stepped over rather than aborting a batch that is
23
- // otherwise fine — see `git-epic-relations.ts` for why the relation is not part of the contract.
23
+ // otherwise fine — see `epic-relations.ts` for why the relation is not part of the contract.
24
24
  async function apply_dependencies(input: {
25
25
  children: ReadonlyArray<number>
26
26
  is_ordered: boolean
27
27
  }): Promise<void> {
28
- const pairs = git_epic_body.build_dependency_pairs(input.children, input.is_ordered)
28
+ const pairs = epic_body.build_dependency_pairs(input.children, input.is_ordered)
29
29
  if (pairs.length === 0) return
30
30
 
31
- const failures = await git_epic_relations.apply_relations(pairs, 'record')
31
+ const failures = await epic_relations.apply_relations(pairs, 'record')
32
32
 
33
33
  console.info(
34
- git_epic_relations.format_relation_report({
34
+ epic_relations.format_relation_report({
35
35
  links: pairs,
36
36
  failures,
37
37
  action: 'record',
@@ -61,7 +61,7 @@ async function fill_run_command(
61
61
  ): Promise<void> {
62
62
  if (epic_number === undefined) {
63
63
  console.info(
64
- `⚠️ Could not read the new epic's number from its URL; the Execution section still says \`${git_epic_body.format_run_command(undefined)}\`.`,
64
+ `⚠️ Could not read the new epic's number from its URL; the Execution section still says \`${epic_body.format_run_command(undefined)}\`.`,
65
65
  )
66
66
 
67
67
  return
@@ -70,7 +70,7 @@ async function fill_run_command(
70
70
  try {
71
71
  await git_gh_command.issue_edit_body(
72
72
  String(epic_number),
73
- git_epic_body.build_epic_body({
73
+ epic_body.build_epic_body({
74
74
  children: input.children,
75
75
  rationale: input.rationale,
76
76
  is_ordered: input.is_ordered,
@@ -90,7 +90,7 @@ async function create_epic(input: CreateEpicInput): Promise<number> {
90
90
  description: EPIC_LABEL_DESCRIPTION,
91
91
  })
92
92
 
93
- const body = git_epic_body.build_epic_body({
93
+ const body = epic_body.build_epic_body({
94
94
  children: input.children,
95
95
  rationale: input.rationale,
96
96
  is_ordered: input.is_ordered,
@@ -107,7 +107,7 @@ async function create_epic(input: CreateEpicInput): Promise<number> {
107
107
  console.info(`📋 Created epic: ${url}`)
108
108
  await apply_dependencies({ children: input.children, is_ordered: input.is_ordered })
109
109
  await fill_run_command(epic_number, input)
110
- console.info(`▶ Run the children with: ${git_epic_body.format_run_command(epic_number)}`)
110
+ console.info(`▶ Run the children with: ${epic_body.format_run_command(epic_number)}`)
111
111
 
112
112
  return SUCCESS_EXIT_CODE
113
113
  }
@@ -123,7 +123,7 @@ interface PromoteEpicInput {
123
123
  // The issue being promoted, or the reason it cannot be. Refused when it already carries the epic
124
124
  // sections — a second append would give the auto-close two task lists to disagree about.
125
125
  async function read_promotion_subject(epic_number: number): Promise<EpicSubject | undefined> {
126
- const subject = git_epic_validate.parse_epic_subject(
126
+ const subject = epic_validate.parse_epic_subject(
127
127
  await git_gh_command.issue_get_labels_and_body(String(epic_number)),
128
128
  )
129
129
 
@@ -133,8 +133,8 @@ async function read_promotion_subject(epic_number: number): Promise<EpicSubject
133
133
  return undefined
134
134
  }
135
135
 
136
- if (git_epic_promote.has_conflicting_tracking(subject.body)) {
137
- const reason = git_epic_promote.conflict_reason(subject.body)
136
+ if (epic_promote.has_conflicting_tracking(subject.body)) {
137
+ const reason = epic_promote.conflict_reason(subject.body)
138
138
 
139
139
  console.error(`✖ Cannot promote #${String(epic_number)}: ${reason}.`)
140
140
 
@@ -144,16 +144,16 @@ async function read_promotion_subject(epic_number: number): Promise<EpicSubject
144
144
  return subject
145
145
  }
146
146
 
147
- // Write the promoted body and apply the label. The label is what `git-epic-close.ts` filters on, so
147
+ // Write the promoted body and apply the label. The label is what `epic-close.ts` filters on, so
148
148
  // an unlabelled issue is one the auto-close skips forever — a failure there is reported rather than
149
149
  // logged past.
150
150
  async function write_promotion(
151
151
  input: PromoteEpicInput,
152
152
  existing_body: string | undefined,
153
153
  ): Promise<number> {
154
- const body = git_epic_promote.build_promoted_body({ ...input, body: existing_body })
154
+ const body = epic_promote.build_promoted_body({ ...input, body: existing_body })
155
155
 
156
- const tracking_error = git_epic_promote.find_tracking_error(body, input.children)
156
+ const tracking_error = epic_promote.find_tracking_error(body, input.children)
157
157
 
158
158
  if (tracking_error !== undefined) {
159
159
  console.error(`✖ ${tracking_error}`)
@@ -190,14 +190,14 @@ async function promote_epic(input: PromoteEpicInput): Promise<number> {
190
190
 
191
191
  console.info(`📋 Promoted #${String(input.epic_number)} to an epic.`)
192
192
  await apply_dependencies({ children: input.children, is_ordered: input.is_ordered })
193
- console.info(`▶ Run the children with: ${git_epic_body.format_run_command(input.epic_number)}`)
193
+ console.info(`▶ Run the children with: ${epic_body.format_run_command(input.epic_number)}`)
194
194
 
195
195
  return SUCCESS_EXIT_CODE
196
196
  }
197
197
 
198
198
  async function check_epic(epic_number: number): Promise<number> {
199
199
  const raw = await git_gh_command.issue_get_labels_and_body(String(epic_number))
200
- const subject = git_epic_validate.parse_epic_subject(raw)
200
+ const subject = epic_validate.parse_epic_subject(raw)
201
201
 
202
202
  if (subject === undefined) {
203
203
  console.error(`✖ Could not read issue #${String(epic_number)}.`)
@@ -205,19 +205,19 @@ async function check_epic(epic_number: number): Promise<number> {
205
205
  return FAILURE_EXIT_CODE
206
206
  }
207
207
 
208
- const results = git_epic_validate.validate_epic(subject)
208
+ const results = epic_validate.validate_epic(subject)
209
209
 
210
- console.info(git_epic_validate.format_check_report(subject.number, results))
210
+ console.info(epic_validate.format_check_report(subject.number, results))
211
211
 
212
- return git_epic_validate.is_epic_valid(results) ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
212
+ return epic_validate.is_epic_valid(results) ? SUCCESS_EXIT_CODE : FAILURE_EXIT_CODE
213
213
  }
214
214
 
215
- const git_epic_run = {
215
+ const epic_run = {
216
216
  parse_created_number,
217
217
  create_epic,
218
218
  promote_epic,
219
219
  check_epic,
220
220
  }
221
221
 
222
- export { git_epic_run, EPIC_LABEL_COLOR, EPIC_LABEL_DESCRIPTION, FAILURE_EXIT_CODE }
222
+ export { epic_run, EPIC_LABEL_COLOR, EPIC_LABEL_DESCRIPTION, FAILURE_EXIT_CODE }
223
223
  export type { CreateEpicInput, PromoteEpicInput }
@@ -1,8 +1,8 @@
1
- import { git_epic_parse } from './git-epic-parse'
1
+ import { epic_parse } from './epic-parse'
2
2
 
3
3
  // Locating a `## <Name>` section inside an epic body, as the shape every rewrite of one needs.
4
4
  //
5
- // It was `git-epic-add-body.ts`'s private half: that file finds the `Dependencies` section so a
5
+ // It was `epic-add-body.ts`'s private half: that file finds the `Dependencies` section so a
6
6
  // declaration is replaced inside it rather than wherever a declaration-shaped line happens to sit.
7
7
  // `## Decisions` needs the identical search, and a second copy of it is the clone `CLAUDE.md`
8
8
  // prohibits — the fence mask especially, since a heading inside a fenced block is an illustration and
@@ -29,13 +29,13 @@ interface SectionRange {
29
29
  end: number
30
30
  }
31
31
 
32
- // Accepts the body or the lines it was already split into: `git-epic-add-body.ts` holds both forms at
32
+ // Accepts the body or the lines it was already split into: `epic-add-body.ts` holds both forms at
33
33
  // different points of one rewrite, and `join`/`split` round-trip exactly, so one function serves both
34
34
  // rather than the caller re-deriving a mask that has to agree with this one.
35
35
  function to_body_lines(source: string | ReadonlyArray<string>): BodyLines {
36
36
  const lines = typeof source === 'string' ? source.split('\n') : source
37
37
 
38
- return { lines, mask: git_epic_parse.fence_mask(lines.join('\n')) }
38
+ return { lines, mask: epic_parse.fence_mask(lines.join('\n')) }
39
39
  }
40
40
 
41
41
  function is_readable(input: BodyLines, index: number): boolean {
@@ -87,12 +87,12 @@ function is_in_range(index: number, range: SectionRange): boolean {
87
87
  return index >= range.start && index < range.end
88
88
  }
89
89
 
90
- const git_epic_sections = {
90
+ const epic_sections = {
91
91
  to_body_lines,
92
92
  find_indices,
93
93
  find_section_range,
94
94
  is_in_range,
95
95
  }
96
96
 
97
- export { git_epic_sections }
97
+ export { epic_sections }
98
98
  export type { BodyLines, SectionRange }
@@ -1,6 +1,6 @@
1
- import { git_epic_parse } from './git-epic-parse'
2
- import { to_issue_reference } from './git-epic-reference'
3
- import { EPIC_LABEL } from './issue-labels'
1
+ import { EPIC_LABEL } from '#scripts/git/issue-labels'
2
+ import { epic_parse } from './epic-parse'
3
+ import { to_issue_reference } from './epic-reference'
4
4
 
5
5
  // Whether an issue is an epic this repository's writers may edit, and why it is not when it is not.
6
6
  //
@@ -47,7 +47,7 @@ function find_epic_shape_error(subject: EpicShapeSubject, body: string): string
47
47
  return `${reference} does not carry the \`${EPIC_LABEL}\` label, so it is not an epic. ${promote_remedy(subject.epic_number)}`
48
48
  }
49
49
 
50
- if (git_epic_parse.has_external_task_list_entry(body)) {
50
+ if (epic_parse.has_external_task_list_entry(body)) {
51
51
  return `${reference} tracks a child in another repository; editing the declared order of a cross-repository epic is joshuafolkken/kit#864's scope, not this command's.`
52
52
  }
53
53
 
@@ -68,14 +68,14 @@ function find_subject_error(
68
68
  }
69
69
 
70
70
  // A declaration an edit can be relative to: exactly one of the two machine-readable forms.
71
- return git_epic_parse.has_machine_readable_declaration(subject.body)
71
+ return epic_parse.has_machine_readable_declaration(subject.body)
72
72
  ? undefined
73
73
  : missing_declaration_error(subject.epic_number)
74
74
  }
75
75
 
76
- const git_epic_shape = {
76
+ const epic_shape = {
77
77
  find_subject_error,
78
78
  }
79
79
 
80
- export { git_epic_shape }
80
+ export { epic_shape }
81
81
  export type { EpicShapeSubject }
@@ -21,12 +21,11 @@ import { epic_report, type EpicNextResult } from './epic-report'
21
21
  // it, so it is not starved by the lanes it is waiting on. A listing that could not be read is not an
22
22
  // idle repository — the same fail-safe answer `epic_busy` gives.
23
23
  //
24
- // **An idle repository takes its first `run:solo` candidate ahead of the ranking**
25
- // (joshuafolkken/kit#2778). An issue carries the label because its defect reaches the verification
26
- // path, and `wip-cap.md` resumes a batch only once that has merged — so an ordinary candidate ranked
27
- // ahead of it would spend a whole wave running alone, then hold the fix back behind its own merge.
28
- // While lanes are running the ranking stands: nothing overtakes a waiting `run:solo` candidate, and
29
- // the ones ahead of it keep their lanes.
24
+ // **The label does not move a candidate up the ranking** (joshuafolkken/kit#2928, reversing
25
+ // joshuafolkken/kit#2778). `run:solo` means "runs alone", and tidying that merely touches the
26
+ // verification path carries it as well as a defect that breaks it. Which of them goes first is the
27
+ // ranking's question — `issue-rank.ts` puts a verification-path defect at the head — so an idle
28
+ // repository takes the candidates in rank order like any other.
30
29
 
31
30
  const SOLO_LABELS: ReadonlySet<string> = new Set([RUN_SOLO_LABEL])
32
31
 
@@ -75,32 +74,21 @@ function none_of(children: ReadonlyArray<EpicChild>, notice: string): SoloSelect
75
74
  return { offered: [], withheld: [...children], notice }
76
75
  }
77
76
 
78
- // The ranking with the first `run:solo` candidate moved to the head — but only into an idle
79
- // repository, the one place it may start. Anywhere else the ranking is returned as it was.
80
- function prefer(children: ReadonlyArray<EpicChild>, read: BusyRead): ReadonlyArray<EpicChild> {
81
- const solo = children.find((child) => is_solo(child))
82
-
83
- if (solo === undefined || read.kind !== 'idle') return children
84
-
85
- return [solo, ...children.filter((child) => child !== solo)]
86
- }
87
-
88
77
  // Where the first `run:solo` candidate sits decides what starts: everything when there is none, the
89
- // candidates ahead of it when lanes are running, and it alone into an idle repository.
78
+ // candidates ahead of it when it is not first, and it alone once it heads an idle repository's ranking.
90
79
  function select_candidates(
91
80
  children: ReadonlyArray<EpicChild>,
92
81
  read: BusyRead,
93
82
  repo: string,
94
83
  ): SoloSelection {
95
- const ordered = prefer(children, read)
96
- const index = ordered.findIndex((child) => is_solo(child))
97
- const solo = ordered[index]
84
+ const index = children.findIndex((child) => is_solo(child))
85
+ const solo = children[index]
98
86
 
99
- if (solo === undefined) return first_of(ordered, ordered.length)
100
- if (index > 0) return first_of(ordered, index)
101
- if (read.kind === 'idle') return first_of(ordered, 1)
87
+ if (solo === undefined) return first_of(children, children.length)
88
+ if (index > 0) return first_of(children, index)
89
+ if (read.kind === 'idle') return first_of(children, 1)
102
90
 
103
- return none_of(ordered, waiting_message(solo.number, repo))
91
+ return none_of(children, waiting_message(solo.number, repo))
104
92
  }
105
93
 
106
94
  // An unreadable or truncated listing keeps nothing: without it a running `run:solo` issue cannot be
@@ -129,11 +117,24 @@ function withhold(result: EpicNextResult, repo: string, selection: SoloSelection
129
117
  }
130
118
  }
131
119
 
120
+ // What decides the selection. `select` by default; `backlog:next` passes one that ranks before it and
121
+ // caps after it (`backlog-rank.ts`), so the gate itself is written once.
122
+ type SoloSelector = (
123
+ children: ReadonlyArray<EpicChild>,
124
+ read: BusyRead,
125
+ repo: string,
126
+ ) => SoloSelection
127
+
132
128
  // Only a `run` answer offers anything, so every other verdict passes through untouched.
133
- function gate(result: EpicNextResult, read: BusyRead, repo: string): SoloGate {
129
+ function gate(
130
+ result: EpicNextResult,
131
+ read: BusyRead,
132
+ repo: string,
133
+ selector: SoloSelector = select,
134
+ ): SoloGate {
134
135
  if (result.verdict !== 'run') return { result }
135
136
 
136
- const selection = select(epic_report.candidates_for_repo(result, repo), read, repo)
137
+ const selection = selector(epic_report.candidates_for_repo(result, repo), read, repo)
137
138
  const gated = withhold(result, repo, selection)
138
139
 
139
140
  return selection.notice === undefined
@@ -141,7 +142,7 @@ function gate(result: EpicNextResult, read: BusyRead, repo: string): SoloGate {
141
142
  : { result: gated, notice: selection.notice }
142
143
  }
143
144
 
144
- const epic_solo = { gate, is_solo, prefer, select }
145
+ const epic_solo = { gate, is_solo, select }
145
146
 
146
- export type { SoloGate, SoloSelection }
147
+ export type { SoloGate, SoloSelection, SoloSelector }
147
148
  export { epic_solo }
@@ -1,19 +1,19 @@
1
- import { git_epic_parse, type DeclarationState } from './git-epic-parse'
2
- import { EPIC_LABEL, has_label_name } from './issue-labels'
3
- import { parse_json_object_safe } from './parse-json-array'
4
- import { epic_subject_schema } from './schemas'
1
+ import { EPIC_LABEL, has_label_name } from '#scripts/git/issue-labels'
2
+ import { parse_json_object_safe } from '#scripts/git/parse-json-array'
3
+ import { epic_subject_schema } from '#scripts/git/schemas'
4
+ import { epic_parse, type DeclarationState } from './epic-parse'
5
5
 
6
6
  // The four requirements an epic has to satisfy are otherwise enforced only by an agent reading the
7
7
  // procedure carefully, and three of the four fail silently: a missing label or a bare `#N` child
8
- // list makes `git-epic-close.ts` skip the epic forever, and prose dependencies make the batch read
8
+ // list makes `epic-close.ts` skip the epic forever, and prose dependencies make the batch read
9
9
  // as unordered so a skipped `--add-blocked-by` is never reported. Checking them here reuses
10
- // `git-epic-parse.ts` rather than restating the patterns, so "what the auto-close can read" and
10
+ // `epic-parse.ts` rather than restating the patterns, so "what the auto-close can read" and
11
11
  // "what this check accepts" are one definition instead of two that can drift.
12
12
 
13
13
  interface EpicSubject {
14
14
  number: number
15
15
  // The epic's own state, as GitHub returned it (`OPEN` / `CLOSED`, casing unnormalized). Only
16
- // `git-epic-add.ts` reads it — to refuse adding a child to a closed epic — so it is optional: a read
16
+ // `epic-add.ts` reads it — to refuse adding a child to a closed epic — so it is optional: a read
17
17
  // that came back without the field leaves it undefined, which the refusal treats as "not confirmed
18
18
  // closed" rather than blocking (joshuafolkken/kit#2337).
19
19
  state?: string | undefined
@@ -65,7 +65,7 @@ function format_children(children: ReadonlyArray<number>): string {
65
65
  }
66
66
 
67
67
  function check_task_list(subject: EpicSubject): CheckResult {
68
- const children = git_epic_parse.parse_task_list_issue_numbers(subject.body)
68
+ const children = epic_parse.parse_task_list_issue_numbers(subject.body)
69
69
  const is_passing = children.length > 0
70
70
 
71
71
  return {
@@ -94,11 +94,11 @@ function describe_dependencies(state: DeclarationState): string {
94
94
  // each of them. The literal is compared against the constant the generator emits, and the chain
95
95
  // against the one the link reader parses, so no two of them can drift into disagreement.
96
96
  function check_dependencies(subject: EpicSubject): CheckResult {
97
- const state = git_epic_parse.read_declaration(subject.body)
97
+ const state = epic_parse.read_declaration(subject.body)
98
98
 
99
99
  return {
100
100
  name: 'dependencies section',
101
- is_passing: git_epic_parse.is_declaration_readable(state),
101
+ is_passing: epic_parse.is_declaration_readable(state),
102
102
  detail: describe_dependencies(state),
103
103
  }
104
104
  }
@@ -107,7 +107,7 @@ function check_dependencies(subject: EpicSubject): CheckResult {
107
107
  // deliberately leaves such an epic open. Reporting it here is what turns "stayed open silently"
108
108
  // into "was told to close it manually".
109
109
  function check_external_children(subject: EpicSubject): CheckResult {
110
- const has_external = git_epic_parse.has_external_task_list_entry(subject.body)
110
+ const has_external = epic_parse.has_external_task_list_entry(subject.body)
111
111
 
112
112
  return {
113
113
  name: 'auto-close eligibility',
@@ -144,12 +144,12 @@ function format_check_report(epic_number: number, results: ReadonlyArray<CheckRe
144
144
  return [...lines, '', summary].join('\n')
145
145
  }
146
146
 
147
- const git_epic_validate = {
147
+ const epic_validate = {
148
148
  parse_epic_subject,
149
149
  validate_epic,
150
150
  is_epic_valid,
151
151
  format_check_report,
152
152
  }
153
153
 
154
- export { git_epic_validate }
154
+ export { epic_validate }
155
155
  export type { CheckResult, EpicSubject }
@@ -2,19 +2,20 @@
2
2
  /**
3
3
  * Create an epic Issue that satisfies every requirement the auto-close and the order warning read.
4
4
  *
5
- * Usage: tsx scripts-ai/epic.ts "<title>" <N1> <N2> ... [--ordered] [--rationale-file <path|->]
5
+ * Usage: tsx scripts/epic/epic.ts "<title>" <N1> <N2> ... [--ordered] [--rationale-file <path|->]
6
6
  * [--origin <owner/repo#N>]
7
- * tsx scripts-ai/epic.ts --promote <N> <N1> <N2> ... [same flags]
8
- * tsx scripts-ai/epic.ts --add <E> <N1> <N2> ... [--before <M> | --after <M>]
7
+ * tsx scripts/epic/epic.ts --promote <N> <N1> <N2> ... [same flags]
8
+ * tsx scripts/epic/epic.ts --add <E> <N1> <N2> ... [--before <M> | --after <M>]
9
9
  * [--decision-file <path|->]
10
- * tsx scripts-ai/epic.ts --remove <E> <M> <N> ... [--decision-file <path|->]
10
+ * tsx scripts/epic/epic.ts --remove <E> <M> <N> ... [--decision-file <path|->]
11
11
  */
12
- import { git_epic_add, type AddChildrenInput } from '../scripts/git/git-epic-add'
13
- import { git_epic_reconcile } from '../scripts/git/git-epic-reconcile'
14
- import { git_epic_remove } from '../scripts/git/git-epic-remove'
15
- import { git_epic_run } from '../scripts/git/git-epic-run'
16
- import { git_gh_command } from '../scripts/git/git-gh-command'
12
+ import { git_gh_command } from '#scripts/git/git-gh-command'
13
+ import { error_text } from '#scripts/lib/error-message'
14
+ import { epic_add, type AddChildrenInput } from './epic-add'
17
15
  import { epic_cli, type AddArguments, type CrossRepoAddTarget } from './epic-cli'
16
+ import { epic_reconcile } from './epic-reconcile'
17
+ import { epic_remove } from './epic-remove'
18
+ import { epic_run } from './epic-run'
18
19
 
19
20
  const ARGV_OFFSET = 2
20
21
  const FLAGS = '[--ordered] [--rationale-file <path|->] [--origin <owner/repo#N>]'
@@ -37,7 +38,7 @@ async function run_promotion(argv: ReadonlyArray<string>): Promise<number> {
37
38
  return FAILURE_EXIT_CODE
38
39
  }
39
40
 
40
- return await git_epic_run.promote_epic({
41
+ return await epic_run.promote_epic({
41
42
  epic_number: parsed.epic_number,
42
43
  children: parsed.children,
43
44
  rationale: epic_cli.read_rationale(parsed.rationale_path),
@@ -67,7 +68,7 @@ function to_add_input(parsed: AddArguments): AddChildrenInput {
67
68
  async function run_qualified_addition(found: CrossRepoAddTarget): Promise<number> {
68
69
  const local = epic_cli.resolve_local_add(found, await git_gh_command.repo_get_name_with_owner())
69
70
 
70
- if (local !== undefined) return await git_epic_add.add_children(to_add_input(local))
71
+ if (local !== undefined) return await epic_add.add_children(to_add_input(local))
71
72
 
72
73
  console.error(epic_cli.format_cross_repo_refusal(found))
73
74
 
@@ -109,7 +110,7 @@ async function run_addition(argv: ReadonlyArray<string>): Promise<number> {
109
110
 
110
111
  if (parsed === undefined) return await refuse_addition(argv)
111
112
 
112
- return await git_epic_add.add_children(to_add_input(parsed))
113
+ return await epic_add.add_children(to_add_input(parsed))
113
114
  }
114
115
 
115
116
  // Deleting a declared order. No position and no `--ordered`: the path itself says which orders go,
@@ -126,7 +127,7 @@ async function run_removal(argv: ReadonlyArray<string>): Promise<number> {
126
127
  return FAILURE_EXIT_CODE
127
128
  }
128
129
 
129
- return await git_epic_remove.remove_order({
130
+ return await epic_remove.remove_order({
130
131
  epic_number: parsed.epic_number,
131
132
  path: parsed.path,
132
133
  decision: epic_cli.read_decision(parsed.decision_path),
@@ -144,7 +145,7 @@ async function run_reconciliation(argv: ReadonlyArray<string>): Promise<number>
144
145
  return FAILURE_EXIT_CODE
145
146
  }
146
147
 
147
- return await git_epic_reconcile.reconcile_epic(parsed.epic_number)
148
+ return await epic_reconcile.reconcile_epic(parsed.epic_number)
148
149
  }
149
150
 
150
151
  async function run_creation(argv: ReadonlyArray<string>): Promise<number> {
@@ -156,7 +157,7 @@ async function run_creation(argv: ReadonlyArray<string>): Promise<number> {
156
157
  return FAILURE_EXIT_CODE
157
158
  }
158
159
 
159
- return await git_epic_run.create_epic({
160
+ return await epic_run.create_epic({
160
161
  title: parsed.title,
161
162
  children: parsed.children,
162
163
  rationale: epic_cli.read_rationale(parsed.rationale_path),
@@ -187,6 +188,6 @@ async function main(): Promise<void> {
187
188
  try {
188
189
  await main()
189
190
  } catch (error) {
190
- console.error(`✖ ${error instanceof Error ? error.message : String(error)}`)
191
+ console.error(`✖ ${error_text.message_of(error)}`)
191
192
  process.exit(FAILURE_EXIT_CODE)
192
193
  }
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
+ import { file_reader } from '#scripts/lib/read-file'
4
5
  import { z } from 'zod'
5
6
  import { fix_gh_packages_logic, type LockfilePackage } from './fix-gh-packages-logic'
6
7
  import { gh_cli_token } from './gh-cli-token'
@@ -19,7 +20,7 @@ const npm_packument_schema = z.looseObject({
19
20
  type ParsedPackages = Record<string, LockfilePackage>
20
21
 
21
22
  function read_file(file_path: string): string {
22
- return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
23
+ return file_reader.read_file_or_empty(file_path)
23
24
  }
24
25
 
25
26
  // Resolution order: NODE_AUTH_TOKEN → a literal token in the project .npmrc → `gh auth token`.
@@ -1,27 +1,29 @@
1
1
  import { git_gh_api_path } from '#scripts/git/git-gh-api-path'
2
2
  import { git_gh_exec } from '#scripts/git/git-gh-exec'
3
3
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
4
- import { execaSync } from 'execa'
5
4
 
6
5
  // The synchronous twin of `git_gh_repo.repo_get_name_with_owner`: same fact, read the same way, but
7
- // through `execaSync` because `josh init` / `josh sync` / `josh doctor` decide before they can await.
6
+ // synchronously because `josh init` / `josh sync` / `josh doctor` decide before they can await.
8
7
  // It asks REST for the same reason — `gh repo view` goes through GraphQL, which a cloud session is
9
8
  // refused (403), and a repository that reads as unresolved makes `init` / `sync` skip
10
9
  // `sonar-project.properties` outright (joshuafolkken/kit#1023). The path comes from the shared
11
10
  // builder rather than being spelled out again here.
12
11
  //
13
- // `timeout` is spread in rather than passed as `undefined`: execa's options type does not admit an
14
- // undefined value for it, and an explicit `undefined` would not mean "no timeout" anyway.
12
+ // An unbounded lookup asks for `NO_BUDGET`, execa's own "no timeout": the shared layer otherwise
13
+ // applies its default budget, which would change what `init` / `sync` wait for.
14
+ const NO_BUDGET = 0
15
+
15
16
  function fetch_repo_name(timeout_ms: number | undefined): string | undefined {
16
- const result = execaSync('gh', ['api', git_gh_api_path.repo_api_path(), '--jq', '.full_name'], {
17
- ...git_gh_exec.direct_environment(),
17
+ const stdout = git_gh_exec.read_gh_api_sync({
18
+ path: git_gh_api_path.repo_api_path(),
19
+ jq_filter: '.full_name',
18
20
  cwd: PROJECT_ROOT,
19
- reject: false,
20
- ...(timeout_ms !== undefined && { timeout: timeout_ms }),
21
+ timeout_ms: timeout_ms ?? NO_BUDGET,
21
22
  })
22
- if (result.exitCode !== 0 || !result.stdout) return undefined
23
23
 
24
- return result.stdout.trim() || undefined
24
+ const name = stdout?.trim()
25
+
26
+ return name === '' ? undefined : name
25
27
  }
26
28
 
27
29
  // The unbounded lookup, for callers whose *writes* depend on the answer. A timeout would surface as
@@ -10,7 +10,7 @@ const GIT_COMMAND_UNIX = '/usr/bin/git'
10
10
  // modules (joshuafolkken/kit#1640) and a copy in each is a second answer to one question.
11
11
  const PORCELAIN_FLAG = '--porcelain'
12
12
  // The untracked-files mode every porcelain status reading passes (`git-command.ts` carries why it is
13
- // passed and why it is `all`), shared so `run-carry-flush.ts`'s own reading cannot drift from it.
13
+ // passed and why it is `all`), shared so another module's own reading cannot drift from it.
14
14
  const UNTRACKED_FILES_FLAG = '--untracked-files=all'
15
15
 
16
16
  function get_git_command(): string {
@@ -75,7 +75,7 @@ async function pull_latest(): Promise<void> {
75
75
  )
76
76
  }
77
77
 
78
- const ISSUE_PREFIX_PATTERN = /^\d+-/u
78
+ const ISSUE_PREFIX_PATTERN = /^(\d+)-/u
79
79
 
80
80
  function has_same_issue_prefix(branch_a: string, branch_b: string): boolean {
81
81
  const prefix_a = ISSUE_PREFIX_PATTERN.exec(branch_a)?.[0]
@@ -84,6 +84,14 @@ function has_same_issue_prefix(branch_a: string, branch_b: string): boolean {
84
84
  return prefix_a !== undefined && prefix_a === prefix_b
85
85
  }
86
86
 
87
+ // The issue a branch names, or `undefined` when it does not lead with one — `1937-lane` and
88
+ // `1937-add-a-scope` both read 1937, while `main` reads nothing.
89
+ function issue_from_branch(branch: string): number | undefined {
90
+ const digits = ISSUE_PREFIX_PATTERN.exec(branch)?.[1]
91
+
92
+ return digits === undefined ? undefined : Number(digits)
93
+ }
94
+
87
95
  // The one rule `check_and_create_branch` refuses on, kept pure so `git-preflight.ts` asks it before
88
96
  // anything is pulled, switched or staged.
89
97
  function is_mismatch(
@@ -133,6 +141,7 @@ const git_branch = {
133
141
  // second copy of the pattern (joshuafolkken/kit#1497). A copy is what let `lane/<N>` ship: nothing
134
142
  // asserted that the name `lane:open` writes is one this function accepts.
135
143
  has_same_issue_prefix,
144
+ issue_from_branch,
136
145
  }
137
146
 
138
147
  export { git_branch }
@@ -411,14 +411,14 @@ async function push(): Promise<void> {
411
411
  // the same thing: it is on the default branch, bringing it up to date before doing something else —
412
412
  // `main-sync.ts` for `josh ms`, `release-cli.ts` / `release-publish.ts` around the release pull
413
413
  // request, `scripts-ai/prep.ts` before it snapshots the overrides, and `git-branch.ts` →
414
- // `pull_latest` — reached from `scripts-ai/git-workflow.ts`, so it runs on every `josh git` /
414
+ // `pull_latest` — reached from `git-workflow.ts`, so it runs on every `josh git` /
415
415
  // `josh pr` started from the default branch, which makes it the hottest of the five rather than a
416
416
  // dormant one. **None of them is
417
417
  // asking to absorb divergence**, so `merge_branch`'s reasoning inverts here: a default branch that
418
418
  // has diverged is a state to fail loudly on rather than to grow a merge commit over. The name says
419
419
  // which of the two this is, as `merge_fast_forward` does beside `merge_branch`.
420
420
  async function pull_fast_forward(): Promise<void> {
421
- await git_spawn.with_output('pull', ['--ff-only'])
421
+ await git_spawn.with_output_remote('pull', ['--ff-only'])
422
422
  }
423
423
 
424
424
  // Every local branch matching a `git branch --list` pattern, one name per line. The boolean below is
@@ -1,9 +1,6 @@
1
+ import { error_text } from '#scripts/lib/error-message'
1
2
  import { has_stderr_field } from './git-gh-exec'
2
3
 
3
- function get_error_message(error: unknown): string {
4
- return error instanceof Error ? error.message : String(error)
5
- }
6
-
7
4
  function get_stderr_from_error(cause: Error): string | undefined {
8
5
  if (!has_stderr_field(cause)) return undefined
9
6
 
@@ -46,7 +43,7 @@ function display_error_details(cause: unknown): void {
46
43
  }
47
44
 
48
45
  function handle(error: unknown): void {
49
- const error_message = get_error_message(error)
46
+ const error_message = error_text.message_of(error)
50
47
 
51
48
  console.error('')
52
49
  console.error('❌ Error:', error_message)