@joshuafolkken/kit 1.1005.0 → 1.1035.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 (272) 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/workflow-commands/SKILL.md +8 -23
  5. package/.claude/skills/workflow-commands/backlogrun-child.md +4 -2
  6. package/.claude/skills/workflow-commands/backlogrun.md +3 -12
  7. package/.claude/skills/workflow-commands/fullrun-steps.md +4 -3
  8. package/.claude/skills/workflow-commands/fullrun.md +1 -1
  9. package/.claude/skills/workflow-commands/halfrun.md +2 -1
  10. package/.claude/skills/workflow-commands/into-target.md +1 -1
  11. package/.claude/skills/workflow-commands/observation-filing.md +42 -43
  12. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  13. package/.codex/hooks.json +1 -5
  14. package/.github/workflows/sonar-qube.yml +1 -1
  15. package/AGENTS.md +4 -25
  16. package/CLAUDE.md +24 -38
  17. package/GEMINI.md +4 -24
  18. package/README.md +22 -58
  19. package/dist/CLAUDE.md +24 -38
  20. package/dist/hooks/batch-guard.js +4 -4
  21. package/dist/hooks/{chunk-WMIR5SNX.js → chunk-D67KVGGY.js} +3 -3
  22. package/dist/hooks/{chunk-EZLHXKJ6.js → chunk-EIFCAJRB.js} +70 -27
  23. package/dist/hooks/{chunk-KPZN42RT.js → chunk-GJT4BNVE.js} +3 -3
  24. package/dist/hooks/{chunk-PAXMLGSS.js → chunk-GLBDBJOK.js} +44 -24
  25. package/dist/hooks/{chunk-GBJUERYK.js → chunk-HOYJU5RM.js} +1 -1
  26. package/dist/hooks/{chunk-6PSTVOLH.js → chunk-HU66PWWL.js} +33 -57
  27. package/dist/hooks/{chunk-A2H5I2VD.js → chunk-KG6PZD72.js} +2 -2
  28. package/dist/hooks/{chunk-AWTFKKFW.js → chunk-XPLP27WA.js} +3 -8
  29. package/dist/hooks/{chunk-4GDR2YTV.js → chunk-Y27M6A7X.js} +4 -4
  30. package/dist/hooks/{chunk-56LGMJJV.js → chunk-ZXNE6NY3.js} +2 -2
  31. package/dist/hooks/codex-hook-adapter.js +10 -10
  32. package/dist/hooks/duplicate-read-guard.js +5 -5
  33. package/dist/hooks/format-edited.js +2 -2
  34. package/dist/hooks/investigation-guard.js +5 -5
  35. package/dist/hooks/pretool-guard.js +9 -9
  36. package/dist/hooks/stop-guard.js +11 -15
  37. package/dist/josh.js +38 -19
  38. package/dist/version/index.js +93 -41
  39. package/eslint/base.js +2 -11
  40. package/package.json +32 -6
  41. package/prompts/coding-standards.md +15 -290
  42. package/prompts/collaboration-workflow/cross-repo-epic.md +2 -51
  43. package/prompts/collaboration-workflow/epic-audit.md +2 -53
  44. package/prompts/collaboration-workflow/epic-bundle.md +2 -106
  45. package/prompts/collaboration-workflow/issue-template.md +4 -4
  46. package/prompts/collaboration-workflow/operating-rules.md +18 -75
  47. package/prompts/collaboration-workflow/principles.md +74 -0
  48. package/prompts/collaboration-workflow/residency.md +54 -14
  49. package/prompts/collaboration-workflow/rule-delivery.md +1 -11
  50. package/prompts/collaboration-workflow/upstream-interrupt.md +1 -1
  51. package/prompts/collaboration-workflow.md +22 -28
  52. package/scripts/adopt/adopt-logic.ts +2 -2
  53. package/scripts/adopt/adopt-toolkits.ts +1 -1
  54. package/scripts/adopt/adopt.ts +2 -2
  55. package/scripts/agent/agent-event.ts +2 -9
  56. package/scripts/agent/agent-exit-record.ts +2 -9
  57. package/scripts/backlog/backlog-budget-cli.ts +2 -6
  58. package/scripts/backlog/backlog-drive-cli.ts +20 -10
  59. package/scripts/backlog/backlog-drive.ts +50 -16
  60. package/scripts/backlog/backlog-offer-cli.ts +6 -5
  61. package/scripts/backlog/backlog-plan.ts +1 -1
  62. package/scripts/build/build-bin.ts +2 -1
  63. package/scripts/build/build-config-merge.ts +2 -1
  64. package/scripts/build/build-hooks.ts +24 -4
  65. package/scripts/build/build-managed-marker.ts +2 -1
  66. package/scripts/build/build-self-sync-guard.ts +2 -1
  67. package/scripts/build/build-version.ts +2 -1
  68. package/scripts/cases/cases-logic.ts +2 -6
  69. package/scripts/ci/pr-classification.ts +13 -11
  70. package/scripts/cost-runtime/cost-transcript.ts +4 -11
  71. package/scripts/doctor/doctor-consumer.ts +5 -12
  72. package/scripts/doctor/doctor-ports.ts +2 -2
  73. package/scripts/document/document-byte-budget.ts +8 -16
  74. package/scripts/document/document-section.ts +2 -6
  75. package/scripts/document/entry-read-budget.ts +1 -1
  76. package/scripts/{git/git-epic-add-body.ts → epic/epic-add-body.ts} +23 -23
  77. package/scripts/{git/git-epic-add-plan.ts → epic/epic-add-plan.ts} +23 -26
  78. package/scripts/{git/git-epic-add.ts → epic/epic-add.ts} +17 -17
  79. package/scripts/epic/epic-audit-cli.ts +4 -4
  80. package/scripts/epic/epic-audit-orphans.ts +2 -2
  81. package/scripts/epic/epic-audit-rationale.ts +1 -1
  82. package/scripts/epic/epic-audit.ts +1 -1
  83. package/scripts/{git/git-epic-body.ts → epic/epic-body.ts} +6 -6
  84. package/scripts/epic/epic-bundle-cli.ts +1 -1
  85. package/scripts/epic/epic-bundle.ts +2 -2
  86. package/scripts/epic/epic-candidate-confirm.ts +2 -1
  87. package/scripts/{git/git-epic-chains.ts → epic/epic-chains.ts} +4 -4
  88. package/{scripts-ai → scripts/epic}/epic-check.ts +5 -4
  89. package/{scripts-ai → scripts/epic}/epic-cli.ts +4 -4
  90. package/scripts/{git/git-epic-close-comment.ts → epic/epic-close-comment.ts} +9 -9
  91. package/scripts/{git/git-epic-close.ts → epic/epic-close.ts} +23 -19
  92. package/scripts/epic/epic-cross-repo.ts +21 -38
  93. package/scripts/{git/git-epic-decision.ts → epic/epic-decision.ts} +12 -14
  94. package/scripts/epic/epic-fetch.ts +3 -3
  95. package/scripts/epic/epic-graph.ts +3 -3
  96. package/scripts/epic/epic-index.ts +2 -2
  97. package/scripts/epic/epic-issue.ts +1 -1
  98. package/scripts/epic/epic-next.ts +3 -3
  99. package/scripts/{git/git-epic-parse.ts → epic/epic-parse.ts} +3 -3
  100. package/scripts/{git/git-epic-promote.ts → epic/epic-promote.ts} +10 -10
  101. package/scripts/{git/git-epic-read.ts → epic/epic-read.ts} +9 -9
  102. package/scripts/{git/git-epic-reconcile.ts → epic/epic-reconcile.ts} +18 -20
  103. package/scripts/{git/git-epic-reference.ts → epic/epic-reference.ts} +4 -23
  104. package/scripts/epic/epic-relation-recheck.ts +4 -3
  105. package/scripts/{git/git-epic-relations.ts → epic/epic-relations.ts} +5 -5
  106. package/scripts/{git/git-epic-remove-plan.ts → epic/epic-remove-plan.ts} +17 -19
  107. package/scripts/{git/git-epic-remove.ts → epic/epic-remove.ts} +13 -13
  108. package/scripts/{git/git-epic-run.ts → epic/epic-run.ts} +27 -27
  109. package/scripts/{git/git-epic-sections.ts → epic/epic-sections.ts} +6 -6
  110. package/scripts/{git/git-epic-shape.ts → epic/epic-shape.ts} +7 -7
  111. package/scripts/{git/git-epic-validate.ts → epic/epic-validate.ts} +13 -13
  112. package/{scripts-ai → scripts/epic}/epic.ts +17 -16
  113. package/scripts/gh/fix-gh-packages.ts +2 -1
  114. package/scripts/gh/gh-spawn.ts +12 -10
  115. package/scripts/git/constants.ts +1 -1
  116. package/scripts/git/git-branch.ts +10 -1
  117. package/scripts/git/git-command.ts +1 -1
  118. package/scripts/git/git-error.ts +2 -5
  119. package/{scripts-ai → scripts/git}/git-followup-finish.ts +7 -7
  120. package/scripts/git/git-followup-flush.ts +74 -48
  121. package/scripts/git/git-followup-issue-close.ts +2 -2
  122. package/scripts/git/git-followup-label.ts +1 -1
  123. package/scripts/git/git-followup-pending.ts +1 -1
  124. package/{scripts-ai → scripts/git}/git-followup-workflow.ts +23 -11
  125. package/scripts/git/git-gh-exec.ts +85 -26
  126. package/scripts/git/git-gh-issue-read.ts +2 -2
  127. package/scripts/git/git-gh-issue-rest.ts +2 -2
  128. package/scripts/git/git-gh-issue-write.ts +6 -6
  129. package/scripts/git/git-gh-pr-read.ts +0 -21
  130. package/scripts/git/git-gh-pr.ts +2 -23
  131. package/scripts/git/git-pr-followup-wrapup.ts +2 -6
  132. package/scripts/git/git-preflight.ts +3 -6
  133. package/scripts/git/git-staging.ts +21 -9
  134. package/{scripts-ai → scripts/git}/git-workflow.ts +11 -11
  135. package/scripts/git/github-issue-url.ts +1 -1
  136. package/scripts/git/issue-labels.ts +1 -1
  137. package/scripts/git/issue-reference.ts +19 -0
  138. package/scripts/git/main-merge.ts +2 -1
  139. package/scripts/git/main-sync.ts +3 -2
  140. package/scripts/git/schemas.ts +1 -1
  141. package/scripts/git/stash-pop-args.ts +7 -13
  142. package/scripts/git/stash-pop-cli.ts +2 -1
  143. package/{scripts-ai → scripts/git}/telegram-test-logic.ts +2 -2
  144. package/{scripts-ai → scripts/git}/telegram-test.ts +7 -7
  145. package/scripts/hooks/format-rewrite-notice.ts +2 -8
  146. package/scripts/init/init-ai-copy.ts +23 -14
  147. package/scripts/init/init.ts +8 -4
  148. package/scripts/init/start-steps.ts +2 -1
  149. package/scripts/init/start.ts +2 -1
  150. package/scripts/issue/issue-comment-cli.ts +2 -1
  151. package/scripts/issue/issue-file-cli.ts +2 -1
  152. package/scripts/issue/issue-fold-cli.ts +9 -16
  153. package/scripts/issue/issue-scout-cli.ts +8 -13
  154. package/scripts/issue/measure-rerun-cli.ts +7 -6
  155. package/scripts/josh/josh-commands-ai.ts +2 -2
  156. package/scripts/josh/josh-commands-backlog.ts +1 -1
  157. package/scripts/josh/josh-commands-development.ts +1 -1
  158. package/scripts/josh/josh-commands-workflow.ts +4 -4
  159. package/scripts/lane/lane-cli.ts +2 -1
  160. package/scripts/lane/lane-open.ts +3 -2
  161. package/scripts/lane/lane-registry.ts +3 -6
  162. package/scripts/lib/cli-flags.ts +20 -2
  163. package/scripts/lib/error-message.ts +9 -0
  164. package/scripts/lib/read-file.ts +26 -2
  165. package/scripts/observations/observation-ledger-home.ts +98 -26
  166. package/scripts/observations/observation-ledger-migrate.ts +26 -21
  167. package/scripts/observations/observation-ledger-prepare.ts +10 -20
  168. package/scripts/observations/observation-ledger.ts +58 -25
  169. package/scripts/observations/observations-flush-cli.ts +6 -23
  170. package/scripts/observations/observations-flush-landing.ts +2 -1
  171. package/scripts/observations/observations-flush.ts +29 -20
  172. package/scripts/package/package-scout-cli.ts +8 -16
  173. package/scripts/ports/port-command.ts +2 -1
  174. package/scripts/propagate/propagate-publish.ts +6 -7
  175. package/scripts/propagate/propagate-steps.ts +2 -1
  176. package/scripts/propagate/propagate.ts +3 -2
  177. package/scripts/refactor/import-graph.ts +2 -6
  178. package/scripts/registry-migration/migrate.ts +3 -2
  179. package/scripts/registry-migration/user-npmrc.ts +2 -2
  180. package/scripts/release/release-cli.ts +2 -1
  181. package/scripts/repo/repo-setting.ts +10 -8
  182. package/scripts/review/review-finding-ledger.ts +3 -3
  183. package/scripts/review/review-findings-cli.ts +4 -11
  184. package/scripts/review/review-record-cli.ts +14 -11
  185. package/scripts/review/review-record.ts +6 -16
  186. package/scripts/rules/decision-oracle.ts +2 -2
  187. package/scripts/rules/rule-body-guard.ts +2 -2
  188. package/scripts/rules/rule-value-cache.ts +99 -0
  189. package/scripts/rules/rule-value-cli.ts +10 -9
  190. package/scripts/rules/rule-value.ts +1 -1
  191. package/scripts/run/run-carry-args.ts +2 -6
  192. package/scripts/run/run-carry-cli.ts +5 -7
  193. package/scripts/run/run-cut-args.ts +7 -13
  194. package/scripts/run/run-ending-cli.ts +4 -7
  195. package/scripts/run/run-event-stream.ts +2 -5
  196. package/scripts/run/run-liveness-cli.ts +4 -7
  197. package/scripts/run/run-progress-args.ts +2 -6
  198. package/scripts/run/run-progress-cli.ts +2 -1
  199. package/scripts/run/run-tail-cli.ts +3 -3
  200. package/scripts/run/run-tidy-stashes.ts +7 -14
  201. package/scripts/run/run-wake-cli.ts +2 -6
  202. package/scripts/run/run-wake-driver.ts +15 -2
  203. package/scripts/run/run-wake-session.ts +14 -4
  204. package/scripts/safe-chain/project-config.ts +3 -6
  205. package/scripts/security/security-audit-provision.ts +6 -7
  206. package/scripts/sonar/sonar-hotspots-cli.ts +2 -1
  207. package/scripts/sync/directory-copy-guard.ts +2 -1
  208. package/scripts/sync/sync-ai-files.ts +3 -3
  209. package/scripts/sync/template-source-logic.ts +3 -4
  210. package/scripts/test/e2e-retry-check.ts +3 -6
  211. package/scripts/ui/ui-routes-cli.ts +3 -6
  212. package/scripts/version/bump-version.ts +2 -1
  213. package/scripts/version/version-remote.ts +18 -27
  214. package/scripts/version/version-targets.ts +3 -3
  215. package/.claude/skills/workflow-commands/rule-residency.md +0 -267
  216. package/evals/scenarios/consult-not-execute.json +0 -20
  217. package/evals/scenarios/no-implicit-workflow.json +0 -26
  218. package/evals/scenarios/overrides-both-locations.json +0 -29
  219. package/evals/scenarios/upstream-interrupt.json +0 -23
  220. package/evals/scenarios/workflow-skill-loaded.json +0 -16
  221. package/prompts/agent-lint-protocol.json +0 -79
  222. package/prompts/agent-rules.md +0 -63
  223. package/prompts/collaboration-workflow/consultation-vs-execution.md +0 -8
  224. package/prompts/collaboration-workflow/distributed-docs.md +0 -7
  225. package/prompts/collaboration-workflow/durable-rules.md +0 -24
  226. package/prompts/collaboration-workflow/latest-first.md +0 -14
  227. package/prompts/collaboration-workflow/no-clones.md +0 -11
  228. package/prompts/collaboration-workflow/simplicity-first.md +0 -9
  229. package/prompts/collaboration-workflow/single-source-rules.md +0 -13
  230. package/scripts/build/shim-shell.ts +0 -25
  231. package/scripts/claude/skill-meta.ts +0 -37
  232. package/scripts/document/document-scan.ts +0 -108
  233. package/scripts/document/generate-catalog.ts +0 -69
  234. package/scripts/document/retired-phrases.ts +0 -143
  235. package/scripts/document/review-agent-type.ts +0 -46
  236. package/scripts/document/single-source.ts +0 -73
  237. package/scripts/git/git-fixture-workspace.ts +0 -88
  238. package/scripts/hooks/format-edited-file-fixtures.ts +0 -109
  239. package/scripts/hooks/prompt-hooks.ts +0 -38
  240. package/scripts/init/hook-command-bootstrap.ts +0 -83
  241. package/scripts/issue/issue-logic.ts +0 -50
  242. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +0 -1227
  243. package/scripts/josh/josh-script-reader.ts +0 -11
  244. package/scripts/package/production-dependency-graph.ts +0 -68
  245. package/scripts/retrospective/retrospective-cli.ts +0 -97
  246. package/scripts/retrospective/retrospective.ts +0 -127
  247. package/scripts/rules/delivered-rules-harness.ts +0 -72
  248. package/scripts/rules/top-level-scripts-guard.ts +0 -47
  249. package/scripts/run/run-carry-flush.ts +0 -58
  250. package/scripts/run/run-transcript.golden.txt +0 -57
  251. package/scripts/run/run-wake-work.ts +0 -108
  252. package/scripts/sync/dependabot-pin-logic.ts +0 -135
  253. package/scripts/test/classify-isolation.ts +0 -149
  254. package/scripts/test/http-fault-injection.ts +0 -79
  255. package/scripts/test/josh-harness-environment.ts +0 -260
  256. package/scripts/test/josh-harness.ts +0 -96
  257. package/scripts/test/pilot-files.ts +0 -452
  258. package/scripts/test/pilot-measure.ts +0 -78
  259. package/scripts/test/test-network-guard.ts +0 -453
  260. package/scripts/test/test-repository-guard.ts +0 -317
  261. package/scripts/test/test-state-guard.ts +0 -85
  262. package/scripts/test/test-stdout-guard.ts +0 -54
  263. package/scripts/test/test-telegram-guard.ts +0 -90
  264. package/scripts/test/unit-guard-environment.ts +0 -12
  265. package/scripts/test/unit-projects.ts +0 -139
  266. package/scripts/test/vitest-include-globs.ts +0 -12
  267. package/scripts/time-runtime/time-batch-guard-fixtures.ts +0 -66
  268. package/scripts-ai/completion-gate.ts +0 -13
  269. package/scripts-ai/environment-loader.ts +0 -9
  270. /package/{scripts-ai → scripts/epic}/epic-cli-argv.ts +0 -0
  271. /package/{scripts-ai → scripts/epic}/epic-cli-remove.ts +0 -0
  272. /package/{scripts-ai → scripts/git}/followup-issue-number.ts +0 -0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
- import { parseArgs } from 'node:util'
3
+ import { cli_flags } from '#scripts/lib/cli-flags'
4
4
  import { z } from 'zod'
5
5
  import {
6
6
  package_scout,
@@ -120,27 +120,19 @@ function parse_size(raw: string | undefined): number {
120
120
  return raw === undefined || !Number.isSafeInteger(parsed) || parsed <= 0 ? DEFAULT_SIZE : parsed
121
121
  }
122
122
 
123
- function parse_arguments(argv: ReadonlyArray<string>): ScoutArguments | undefined {
124
- const { values, positionals } = parseArgs({
123
+ // An unknown flag is `undefined` rather than a throw, so the answer is the usage line rather than a
124
+ // stack trace, on a command whose output a workflow reads.
125
+ function read_arguments(argv: ReadonlyArray<string>): ScoutArguments | undefined {
126
+ const parsed = cli_flags.parse_or_undefined({
125
127
  args: [...argv],
126
128
  options: { size: { type: 'string' } },
127
129
  allowPositionals: true,
128
130
  })
129
- const keywords = positionals.join(' ').trim()
130
-
131
- if (keywords === '') return undefined
131
+ const keywords = parsed?.positionals.join(' ').trim() ?? ''
132
132
 
133
- return { keywords, size: parse_size(values.size) }
134
- }
133
+ if (parsed === undefined || keywords === '') return undefined
135
134
 
136
- // An unknown flag makes `parseArgs` throw. Caught so the answer is the usage line rather than a stack
137
- // trace, on a command whose output a workflow reads.
138
- function read_arguments(argv: ReadonlyArray<string>): ScoutArguments | undefined {
139
- try {
140
- return parse_arguments(argv)
141
- } catch {
142
- return undefined
143
- }
135
+ return { keywords, size: parse_size(parsed.values.size) }
144
136
  }
145
137
 
146
138
  async function report(args: ScoutArguments): Promise<number> {
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { ports, type PortEnvironment } from '#ports'
4
+ import { error_text } from '#scripts/lib/error-message'
4
5
 
5
6
  const SUCCESS_EXIT_CODE = 0
6
7
  const USAGE_EXIT_CODE = 1
@@ -49,7 +50,7 @@ function run(argv: ReadonlyArray<string>, environment?: PortEnvironment): PortRe
49
50
  return { text: String(RESOLVERS[name](environment)), exit_code: SUCCESS_EXIT_CODE }
50
51
  } catch (error) {
51
52
  return {
52
- text: error instanceof Error ? error.message : String(error),
53
+ text: error_text.message_of(error),
53
54
  exit_code: FAILURE_EXIT_CODE,
54
55
  }
55
56
  }
@@ -1,7 +1,6 @@
1
1
  import { git_gh_exec } from '#scripts/git/git-gh-exec'
2
2
  import { poll } from '#scripts/lib/poll'
3
3
  import { with_page_size } from '#scripts/version/version-remote'
4
- import { execaSync } from 'execa'
5
4
 
6
5
  // Waiting for a specific version of this package to actually exist in the registry.
7
6
  //
@@ -58,15 +57,15 @@ function finish(state: PublishWaitState, version: string, attempts: number): Pub
58
57
  // reported apart, because one means wait longer and the other means something is broken.
59
58
  function fetch_published_versions(versions_endpoint: string): Array<string> | undefined {
60
59
  const endpoint = with_page_size(versions_endpoint, VERSIONS_PAGE_SIZE)
61
- const result = execaSync('gh', ['api', endpoint, '--jq', NAMES_JQ], {
62
- ...git_gh_exec.direct_environment(),
63
- reject: false,
64
- timeout: GH_TIMEOUT_MS,
60
+ const stdout = git_gh_exec.read_gh_api_sync({
61
+ path: endpoint,
62
+ jq_filter: NAMES_JQ,
63
+ timeout_ms: GH_TIMEOUT_MS,
65
64
  })
66
- if (result.exitCode !== 0) return undefined
65
+ if (stdout === undefined) return undefined
67
66
 
68
67
  try {
69
- const parsed: unknown = JSON.parse(result.stdout)
68
+ const parsed: unknown = JSON.parse(stdout)
70
69
 
71
70
  return Array.isArray(parsed) ? parsed.filter((name) => typeof name === 'string') : undefined
72
71
  } catch {
@@ -3,6 +3,7 @@ import { git_gh_exec } from '#scripts/git/git-gh-exec'
3
3
  import { git_gh_issue_write } from '#scripts/git/git-gh-issue-write'
4
4
  import { OTHER_CHANGE_LABEL } from '#scripts/git/issue-labels'
5
5
  import { GATE_COMMAND } from '#scripts/josh/josh-command-types'
6
+ import { error_text } from '#scripts/lib/error-message'
6
7
  import { build_upgrade_shell_command } from '#scripts/version/upgrade-shell-command'
7
8
  import { create_version_command_config } from '#scripts/version/version-command-config'
8
9
  import { execaSync } from 'execa'
@@ -235,7 +236,7 @@ function precheck_step(target: PropagateTarget, step: string): StepResult {
235
236
  // multi-line detail would print raw JSON as an unindented second line and glue that warning to the
236
237
  // end of it, where nobody reading the report would see it.
237
238
  function to_issue_detail(error: unknown): string {
238
- const message = error instanceof Error ? error.message : String(error)
239
+ const message = error_text.message_of(error)
239
240
 
240
241
  return message.replaceAll('\n', ' ').trim()
241
242
  }
@@ -2,7 +2,7 @@
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { repo_discovery } from '#scripts/discovery/repo-discovery'
4
4
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
5
- import { refuse_unknown_flags } from '#scripts/lib/cli-flags'
5
+ import { cli_flags } from '#scripts/lib/cli-flags'
6
6
  import { status_icons } from '#scripts/lib/status-icons'
7
7
  import { self_sync_guard } from '#scripts/self-sync-guard/self-sync-guard-logic'
8
8
  import { KIT_PACKAGE_NAME } from '#scripts/version/kit-descriptor'
@@ -72,7 +72,8 @@ function parse_options(argv: ReadonlyArray<string>): RunOptions {
72
72
  ...(target !== undefined && { target }),
73
73
  }
74
74
  const usage =
75
- target_usage ?? refuse_unknown_flags(rest, [...KNOWN_FLAGS, TARGET_USAGE], 'propagate')
75
+ target_usage ??
76
+ cli_flags.refuse_unknown_flags(rest, [...KNOWN_FLAGS, TARGET_USAGE], 'propagate')
76
77
 
77
78
  return usage === undefined ? options : { ...options, usage }
78
79
  }
@@ -1,5 +1,5 @@
1
- import { readFileSync } from 'node:fs'
2
1
  import path from 'node:path'
2
+ import { file_reader } from '#scripts/lib/read-file'
3
3
 
4
4
  // The scope expansion `prompts/refactoring.md` §4.3 used to describe in prose (joshuafolkken/kit#2180):
5
5
  // from the seed files, follow imports both ways — the files a seed imports and the files that import a
@@ -67,11 +67,7 @@ function resolve_spec(
67
67
  }
68
68
 
69
69
  function read_source(file: string): string {
70
- try {
71
- return readFileSync(file, 'utf8')
72
- } catch {
73
- return ''
74
- }
70
+ return file_reader.read_if_readable(file) ?? ''
75
71
  }
76
72
 
77
73
  function file_imports(file: string, root: string, known: ReadonlySet<string>): ReadonlySet<string> {
@@ -1,6 +1,7 @@
1
- import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
1
+ import { existsSync, rmSync, 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 { execa } from 'execa'
5
6
  import { z } from 'zod'
6
7
  import { migrate_logic, type MigrationPlan } from './migrate-logic'
@@ -70,7 +71,7 @@ interface MigrationContext {
70
71
  }
71
72
 
72
73
  function read_file(file_path: string): string {
73
- return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
74
+ return file_reader.read_file_or_empty(file_path)
74
75
  }
75
76
 
76
77
  function has_registry_override(environment: NodeJS.ProcessEnv): boolean {
@@ -1,13 +1,13 @@
1
- import { existsSync, readFileSync } from 'node:fs'
2
1
  import os from 'node:os'
3
2
  import path from 'node:path'
3
+ import { file_reader } from '#scripts/lib/read-file'
4
4
 
5
5
  const NPMRC = '.npmrc'
6
6
 
7
7
  function read(home: string = os.homedir()): string {
8
8
  const file = path.join(home, NPMRC)
9
9
 
10
- return existsSync(file) ? readFileSync(file, 'utf8') : ''
10
+ return file_reader.read_file_or_empty(file)
11
11
  }
12
12
 
13
13
  const user_npmrc = { read }
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env tsx
2
2
  import { fileURLToPath } from 'node:url'
3
3
  import { git_command } from '#scripts/git/git-command'
4
+ import { error_text } from '#scripts/lib/error-message'
4
5
  import { release_history } from './release-history'
5
6
  import { release_plan, type ReleasePlan } from './release-plan'
6
7
  import { release_publish } from './release-publish'
@@ -76,7 +77,7 @@ async function main(): Promise<void> {
76
77
  try {
77
78
  process.exit(await run(is_dry_run_requested(process.argv)))
78
79
  } catch (error) {
79
- console.error(error instanceof Error ? error.message : String(error))
80
+ console.error(error_text.message_of(error))
80
81
  process.exit(FAILURE_EXIT_CODE)
81
82
  }
82
83
  }
@@ -1,7 +1,5 @@
1
1
  import { git_gh_exec } from '#scripts/git/git-gh-exec'
2
2
  import { PROJECT_ROOT } from '#scripts/init/init-paths'
3
- import { read_spawn_stdout } from '#scripts/lib/spawn-exit'
4
- import { execaSync } from 'execa'
5
3
 
6
4
  // The shared half of every "report a GitHub repository setting kit cannot write" check.
7
5
  //
@@ -27,17 +25,21 @@ interface RepoApiResult {
27
25
  stdout: string
28
26
  }
29
27
 
28
+ const FAILED_EXIT_CODE = 1
29
+
30
30
  // One `gh api` call. Never throws and never propagates a non-zero exit: an unreadable answer is a
31
- // reported status, not a command failure.
31
+ // reported status, not a command failure. Every failure reads as `FAILED_EXIT_CODE` with no output,
32
+ // which `parse_payload` treats as unreadable whatever gh's own exit code was.
32
33
  function query_repo_api(api_path: string): RepoApiResult {
33
- const result = execaSync('gh', ['api', api_path], {
34
- ...git_gh_exec.direct_environment(),
34
+ const stdout = git_gh_exec.read_gh_api_sync({
35
+ path: api_path,
35
36
  cwd: PROJECT_ROOT,
36
- reject: false,
37
- timeout: GH_TIMEOUT_MS,
37
+ timeout_ms: GH_TIMEOUT_MS,
38
38
  })
39
39
 
40
- return { exit_code: result.exitCode ?? 1, stdout: read_spawn_stdout(result) }
40
+ return stdout === undefined
41
+ ? { exit_code: FAILED_EXIT_CODE, stdout: '' }
42
+ : { exit_code: 0, stdout }
41
43
  }
42
44
 
43
45
  // The response body as an object, or nothing. A non-zero exit covers every case where the answer
@@ -1,9 +1,9 @@
1
1
  // The review-finding line's grammar, its category vocabulary and the aggregation over it — the single
2
2
  // place all three are defined (joshuafolkken/kit#2325). A `/code-review` round's findings evaporated
3
3
  // the moment they were fixed, so no one could say which category recurred. They now land in the same
4
- // append-only ledger the observation lines use (`docs/maintainers/observations.md`), under a distinct `- rf:`
5
- // prefix so `observation-ledger-line.ts`'s `- k:` grammar never treats them as its own — one ledger
6
- // file, one flush path, a second line *type* rather than a second ledger.
4
+ // append-only ledger the observation lines use (`docs/maintainers/observations/`), under a distinct `- rf:`
5
+ // prefix so `observation-ledger-line.ts`'s `- k:` grammar never treats them as its own — one ledger,
6
+ // one commit path, a second line *type* rather than a second ledger.
7
7
 
8
8
  const FINDING_PREFIX = '- rf:'
9
9
  const FIELD_SEPARATOR = ' | '
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env tsx
2
- import { readFile } from 'node:fs/promises'
3
2
  import { fileURLToPath } from 'node:url'
4
3
  import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
5
4
  import { review_finding_ledger, type CategoryCount } from './review-finding-ledger'
@@ -29,16 +28,10 @@ function format_report(counts: ReadonlyArray<CategoryCount>, zero_rounds: number
29
28
  return `findings by category:\n${body}\n${zero_line(zero_rounds)}`
30
29
  }
31
30
 
32
- async function read_ledger(ledger_path: string): Promise<string> {
33
- try {
34
- return await readFile(ledger_path, 'utf8')
35
- } catch {
36
- return ''
37
- }
38
- }
39
-
40
- async function run(ledger_path: string = observation_ledger_home.ledger_path()): Promise<number> {
41
- const content = await read_ledger(ledger_path)
31
+ // Every issue's file of the ledger directory is counted (joshuafolkken/kit#2919): a recurring category
32
+ // is one whichever issue recorded each sighting.
33
+ async function run(root: string = observation_ledger_home.ledger_root()): Promise<number> {
34
+ const content = (await observation_ledger_home.read(root)) ?? ''
42
35
  const counts = review_finding_ledger.category_counts(content)
43
36
 
44
37
  console.info(format_report(counts, review_finding_ledger.zero_round_count(content)))
@@ -7,8 +7,8 @@ import { review_record, type RecordVerdict } from './review-record'
7
7
 
8
8
  // `josh review:record --issue <N> [<category>:<severity>:<file> ...]` — the one write path for a
9
9
  // `/code-review` round's findings (joshuafolkken/kit#2325). It appends a `- rf:` line per finding to
10
- // the observation ledger, which `pnpm josh observations:flush` then commits like any other ledger
11
- // change. **A call with no findings is a zero-finding round, and it still writes one line** — so the
10
+ // the issue's own file of the observation ledger, which the run's commit carries like any other
11
+ // ledger change. **A call with no findings is a zero-finding round, and it still writes one line** — so the
12
12
  // round that found nothing is recorded rather than mistaken for a round nobody reviewed.
13
13
  //
14
14
  // `josh review:record --check --issue <N>` is the read half `pnpm josh followup` runs before it
@@ -121,14 +121,14 @@ function check_line(verdict: RecordVerdict): string {
121
121
  return verdict.status === 'ok' ? RECORDED_LINE : NOT_REQUIRED_LINE
122
122
  }
123
123
 
124
- async function run_check(issue: string | undefined, ledger_path: string): Promise<number> {
124
+ async function run_check(issue: string | undefined, root: string): Promise<number> {
125
125
  if (issue === undefined || !ISSUE_PATTERN.test(issue)) {
126
126
  console.error(CHECK_USAGE)
127
127
 
128
128
  return FAILURE_EXIT_CODE
129
129
  }
130
130
 
131
- const verdict = await review_record.check(Number(issue), ledger_path)
131
+ const verdict = await review_record.check(Number(issue), root)
132
132
 
133
133
  if (verdict.status === 'ok' || verdict.status === 'not-required') {
134
134
  console.info(check_line(verdict))
@@ -141,7 +141,9 @@ async function run_check(issue: string | undefined, ledger_path: string): Promis
141
141
  return FAILURE_EXIT_CODE
142
142
  }
143
143
 
144
- async function run_record(parsed: Parsed, now: Date, ledger_path: string): Promise<number> {
144
+ // The round lands in its own issue's file (joshuafolkken/kit#2919), so two lanes recording at once
145
+ // write two files, and their pull requests never conflict on the ledger.
146
+ async function run_record(parsed: Parsed, now: Date, root: string): Promise<number> {
145
147
  const request = to_request(parsed)
146
148
 
147
149
  if (request === undefined) {
@@ -151,6 +153,7 @@ async function run_record(parsed: Parsed, now: Date, ledger_path: string): Promi
151
153
  }
152
154
 
153
155
  const lines = build_lines(request, today(now))
156
+ const ledger_path = observation_ledger_home.issue_path(request.issue, root)
154
157
 
155
158
  await observation_ledger_home.append(ledger_path, lines)
156
159
  console.info(confirmation(lines.length, ledger_path))
@@ -158,13 +161,13 @@ async function run_record(parsed: Parsed, now: Date, ledger_path: string): Promi
158
161
  return 0
159
162
  }
160
163
 
161
- // **The default is the primary checkout's ledger, even inside a lane** (joshuafolkken/kit#2419). A line
162
- // appended to a lane's own copy never reached the default branch — the lane's flush is refused and
163
- // `lane:close` does not carry the file — while `--check` read that same copy and let the merge through.
164
+ // **The default is the work tree the command runs in, a lane's inside a lane** (joshuafolkken/kit#2919).
165
+ // The line rides that tree's own commit or `pnpm josh followup`'s pre-merge ledger commit, and
166
+ // `--check` reads the same tree, so the two halves can never disagree about where the round is.
164
167
  async function run(
165
168
  argv: ReadonlyArray<string>,
166
169
  now: Date,
167
- ledger_path: string = observation_ledger_home.ledger_path(),
170
+ root: string = observation_ledger_home.ledger_root(),
168
171
  ): Promise<number> {
169
172
  const parsed = parse_argv(argv)
170
173
 
@@ -174,9 +177,9 @@ async function run(
174
177
  return FAILURE_EXIT_CODE
175
178
  }
176
179
 
177
- if (parsed.check) return await run_check(parsed.issue, ledger_path)
180
+ if (parsed.check) return await run_check(parsed.issue, root)
178
181
 
179
- return await run_record(parsed, now, ledger_path)
182
+ return await run_record(parsed, now, root)
180
183
  }
181
184
 
182
185
  async function main(argv: ReadonlyArray<string>): Promise<void> {
@@ -1,4 +1,3 @@
1
- import { readFile } from 'node:fs/promises'
2
1
  import { observation_ledger_home } from '#scripts/observations/observation-ledger-home'
3
2
  import { review_finding_ledger } from './review-finding-ledger'
4
3
 
@@ -29,24 +28,15 @@ interface RecordVerdict {
29
28
  issue?: number
30
29
  }
31
30
 
32
- // `undefined` for both "the file is absent" and "the file could not be read" — they are the same
33
- // answer here, a ledger this checkout does not keep, and the caller turns that into `not-required`.
34
- async function read_ledger(ledger_path: string): Promise<string | undefined> {
35
- try {
36
- return await readFile(ledger_path, 'utf8')
37
- } catch {
38
- return undefined
39
- }
40
- }
41
-
42
- // The default is the primary checkout's ledger, the one `review:record` writes from a lane
43
- // (joshuafolkken/kit#2419) — a cwd-relative default read the lane's copy, so the followup gate refused
44
- // every round recorded from a lane (joshuafolkken/kit#2431).
31
+ // The default is the work tree the check runs in — the one `review:record` wrote to, a lane's in a
32
+ // lane (joshuafolkken/kit#2919). Every file of the directory is read, so the round is found whichever
33
+ // file holds it; a directory that is not there answers `undefined`, a ledger this checkout does not
34
+ // keep, which becomes `not-required`.
45
35
  async function check(
46
36
  issue: number,
47
- ledger_path: string = observation_ledger_home.ledger_path(),
37
+ root: string = observation_ledger_home.ledger_root(),
48
38
  ): Promise<RecordVerdict> {
49
- const content = await read_ledger(ledger_path)
39
+ const content = await observation_ledger_home.read(root)
50
40
 
51
41
  if (content === undefined) return { status: 'not-required' }
52
42
  if (review_finding_ledger.has_issue_record(content, issue)) return { status: 'ok', issue }
@@ -47,7 +47,7 @@ const HUMAN_REVIEW = 'human-review'
47
47
  // test. `LIVE` is the silent norm; `STOPPED` and `UNKNOWN` lead the difference lines.
48
48
  const LIVE = 'live'
49
49
  const STOPPED = 'stopped'
50
- // The verdicts `epic --reconcile` prints; kept in step with the `git_epic_reconcile` constants in its
50
+ // The verdicts `epic --reconcile` prints; kept in step with the `epic_reconcile` constants in its
51
51
  // test.
52
52
  const RECONCILED = 'reconciled'
53
53
  const NOTHING_TO_RECONCILE = 'nothing to reconcile'
@@ -302,7 +302,7 @@ const DECISION_ORACLES: ReadonlyArray<DecisionOracle> = [
302
302
  decision: 'Whether cross-file or cross-repository code duplication exists',
303
303
  args: '',
304
304
  vocabulary: ['clean', 'clones:'],
305
- single_source: 'prompts/collaboration-workflow/no-clones.md',
305
+ single_source: 'prompts/collaboration-workflow/principles.md → クローン禁止',
306
306
  },
307
307
  {
308
308
  name: 'epic:reconcile',
@@ -1,5 +1,5 @@
1
- import { existsSync, readFileSync } from 'node:fs'
2
1
  import { json_value } from '#scripts/lib/json-value'
2
+ import { file_reader } from '#scripts/lib/read-file'
3
3
  import type { GuardedCall } from '#scripts/time-runtime/time-batch-guard'
4
4
  import { bash_triggers } from './bash-triggers'
5
5
  import { shell_segments } from './shell-segments'
@@ -81,7 +81,7 @@ function string_field(input: Record<string, unknown>, key: string): string {
81
81
  // measure against. Injected so the suite controls it without touching the real filesystem, the way
82
82
  // `file-body.ts` injects its existence check. A new file reads as empty, so its whole body is added.
83
83
  function read_existing(file_path: string): string {
84
- return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
84
+ return file_reader.read_file_or_empty(file_path)
85
85
  }
86
86
 
87
87
  function texts_of(
@@ -0,0 +1,99 @@
1
+ import { spawn } from 'node:child_process'
2
+ import { mkdirSync, renameSync, statSync, writeFileSync } from 'node:fs'
3
+ import path from 'node:path'
4
+ import { file_reader } from '#scripts/lib/read-file'
5
+
6
+ // The `rule:value` reading at the `backlogrun` loop head, without the loop waiting on it
7
+ // (joshuafolkken/kit#2881). Measuring re-reads every transcript and takes minutes, and `backlog:offer`
8
+ // ran it synchronously on every iteration, so each dispatch waited on it. The loop head now prints the
9
+ // last reading from a cache and, when the last refresh is old enough, starts one in a detached process
10
+ // — `josh rule:value --refresh` — so the next iteration prints the newer reading.
11
+ //
12
+ // **It reports and never fails**, as `rule:value` itself does: a missing cache, an unwritable directory
13
+ // or a refresh that could not start prints at most nothing, and never changes the offer's answer.
14
+
15
+ const CACHE_DIRECTORY = ['node_modules', '.cache', 'josh']
16
+ const CACHE_FILE = 'rule-value.txt'
17
+ // Touched when a refresh starts, so the loop head starts at most one per interval however often it runs.
18
+ const STAMP_FILE = 'rule-value.stamp'
19
+ const MS_PER_MINUTE = 60_000
20
+ const REFRESH_MINUTES = 15
21
+ const REFRESH_MS = REFRESH_MINUTES * MS_PER_MINUTE
22
+ const REFRESH_ARGV = ['josh', 'rule:value', '--refresh']
23
+ const PNPM = 'pnpm'
24
+ const TEMPORARY_SUFFIX = '.tmp'
25
+
26
+ interface CachePorts {
27
+ now: () => number
28
+ start_refresh: (cwd: string) => void
29
+ }
30
+
31
+ function cache_path(cwd: string, file: string): string {
32
+ return path.join(cwd, ...CACHE_DIRECTORY, file)
33
+ }
34
+
35
+ function read_cached(cwd: string): string | undefined {
36
+ return file_reader.read_if_readable(cache_path(cwd, CACHE_FILE))
37
+ }
38
+
39
+ function is_refresh_due(cwd: string, now_ms: number): boolean {
40
+ try {
41
+ return now_ms - statSync(cache_path(cwd, STAMP_FILE)).mtimeMs >= REFRESH_MS
42
+ } catch {
43
+ return true
44
+ }
45
+ }
46
+
47
+ function write_cache_file(cwd: string, file: string, text: string): void {
48
+ const target = cache_path(cwd, file)
49
+ const temporary = `${target}${TEMPORARY_SUFFIX}`
50
+
51
+ mkdirSync(path.dirname(target), { recursive: true })
52
+ writeFileSync(temporary, text)
53
+ renameSync(temporary, target)
54
+ }
55
+
56
+ function ignore_spawn_error(): void {
57
+ /* a refresh that could not start leaves the last reading in place */
58
+ }
59
+
60
+ // Detached with no descriptor shared, so the loop head exits — and a backgrounded caller completes —
61
+ // without waiting on the measurement.
62
+ function start_refresh_default(cwd: string): void {
63
+ const child = spawn(PNPM, REFRESH_ARGV, { cwd, detached: true, stdio: 'ignore' })
64
+
65
+ child.on('error', ignore_spawn_error)
66
+ child.unref()
67
+ }
68
+
69
+ const DEFAULT_PORTS: CachePorts = { now: Date.now, start_refresh: start_refresh_default }
70
+
71
+ function refresh_if_due(cwd: string, ports: CachePorts): void {
72
+ if (!is_refresh_due(cwd, ports.now())) return
73
+
74
+ write_cache_file(cwd, STAMP_FILE, new Date(ports.now()).toISOString())
75
+ ports.start_refresh(cwd)
76
+ }
77
+
78
+ // The loop-head call: the last reading to stderr, then a refresh when one is due.
79
+ function emit(cwd: string = process.cwd(), ports: CachePorts = DEFAULT_PORTS): void {
80
+ const cached = read_cached(cwd)
81
+
82
+ if (cached !== undefined) process.stderr.write(cached)
83
+
84
+ try {
85
+ refresh_if_due(cwd, ports)
86
+ } catch {
87
+ // A refresh that could not be recorded or started leaves the last reading in place.
88
+ }
89
+ }
90
+
91
+ // The detached refresh's body: the reading written whole, so a reader never sees half of one.
92
+ function store(text: string, cwd: string = process.cwd()): void {
93
+ write_cache_file(cwd, CACHE_FILE, `${text}\n`)
94
+ }
95
+
96
+ const rule_value_cache = { CACHE_FILE, REFRESH_MS, STAMP_FILE, cache_path, emit, store }
97
+
98
+ export type { CachePorts }
99
+ export { rule_value_cache }
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url'
3
3
  import { cost_transcript, type SessionFile } from '#scripts/cost-runtime/cost-transcript'
4
4
  import { delivered_rules } from './delivered-rules'
5
5
  import { rule_value, type RuleReading } from './rule-value'
6
+ import { rule_value_cache } from './rule-value-cache'
6
7
 
7
8
  // `josh rule:value` — what each delivered rule earns on the channel that carries it, one row per rule
8
9
  // (joshuafolkken/kit#2271). `rule_value.measure` was public but had no caller outside its own test, so
@@ -14,6 +15,8 @@ import { rule_value, type RuleReading } from './rule-value'
14
15
  // no measurable transcript prints `NO_TARGETS` and exits zero rather than erroring, so a fresh
15
16
  // checkout reads differently from a broken command.
16
17
 
18
+ const ARGV_OFFSET = 2
19
+ const REFRESH_FLAG = '--refresh'
17
20
  const PERCENT_SIGN = '%'
18
21
  const COLUMN_GAP = ' '
19
22
  const NO_TARGETS = 'no measurement targets'
@@ -98,24 +101,22 @@ function render(runs: ReadonlyArray<ReadonlyArray<string>>): string {
98
101
  .join('\n')
99
102
  }
100
103
 
101
- // The loop-head call: `backlog:offer` writes this to stderr once per iteration, so a rule that never
102
- // fires appears as a printed row rather than as something a person has to go looking for.
103
- function emit(cwd: string = process.cwd()): void {
104
- process.stderr.write(`${render(gather_runs(cwd))}\n`)
105
- }
104
+ // `--refresh` is the detached measurement the `backlogrun` loop head starts: the reading goes to the
105
+ // cache `backlog:offer` prints from, never to stdout (`rule-value-cache.ts`, joshuafolkken/kit#2881).
106
+ function run_rule_value(argv: ReadonlyArray<string> = [], cwd: string = process.cwd()): number {
107
+ const reading = render(gather_runs(cwd))
106
108
 
107
- function run_rule_value(cwd: string = process.cwd()): number {
108
- process.stdout.write(`${render(gather_runs(cwd))}\n`)
109
+ if (argv.includes(REFRESH_FLAG)) rule_value_cache.store(reading, cwd)
110
+ else process.stdout.write(`${reading}\n`)
109
111
 
110
112
  return 0
111
113
  }
112
114
 
113
115
  if (process.argv[1] === fileURLToPath(import.meta.url)) {
114
- process.exitCode = run_rule_value()
116
+ process.exitCode = run_rule_value(process.argv.slice(ARGV_OFFSET))
115
117
  }
116
118
 
117
119
  const rule_value_cli = {
118
- emit,
119
120
  gather_runs,
120
121
  group_by_run,
121
122
  render,
@@ -9,7 +9,7 @@ import { delivered_rules, type MeasuredRule } from './delivered-rules'
9
9
  // (joshuafolkken/kit#1525).
10
10
  //
11
11
  // **It exists because the eviction order was decided by protection rather than by value.**
12
- // `.claude/skills/workflow-commands/rule-residency.md` recorded that when a rule is trimmed to keep a
12
+ // `prompts/collaboration-workflow/residency.md` records that when a rule is trimmed to keep a
13
13
  // byte count, the sentence that goes is the one no marker pinned (joshuafolkken/kit#951) — so the
14
14
  // least-defended text leaves, never the least-useful. Nothing in the repository could tell the two
15
15
  // apart: `scripts/josh/hook-decision.ts` keeps a once-per-run stamp that answers "has this fired
@@ -1,4 +1,4 @@
1
- import { parseArgs } from 'node:util'
1
+ import { cli_flags } from '#scripts/lib/cli-flags'
2
2
  import { run_carry, type CarryChange, type CarryClaimRequest, type CarryOwner } from './run-carry'
3
3
  import { run_issue_number } from './run-issue-number'
4
4
 
@@ -76,11 +76,7 @@ type Request =
76
76
  const READ_REQUEST: Request = { kind: 'read' }
77
77
 
78
78
  function read_arguments(argv: ReadonlyArray<string>): ParsedValues | undefined {
79
- try {
80
- return parseArgs({ args: [...argv], options: OPTIONS, strict: true }).values
81
- } catch {
82
- return undefined
83
- }
79
+ return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values
84
80
  }
85
81
 
86
82
  function text_of(value: OptionValue): string | undefined {
@@ -10,7 +10,6 @@ import {
10
10
  type RunCarry,
11
11
  } from './run-carry'
12
12
  import { run_carry_args, type CountRequest, type Request } from './run-carry-args'
13
- import { run_carry_flush } from './run-carry-flush'
14
13
  import { run_carry_stash } from './run-carry-stash'
15
14
  import { run_event_stream } from './run-event-stream'
16
15
  import { run_event_stream_emit } from './run-event-stream-emit'
@@ -344,16 +343,15 @@ async function count(
344
343
  // **A stop over a record that is still there pushes one ⏸️ confirmation before the record goes**
345
344
  // (joshuafolkken/kit#2136). `plan` is read from the record `--end` is about to remove, so the second
346
345
  // `--end` reads `none` and plans nothing — one stop never notifies twice. A clean `--end` passes no
347
- // reason and stays silent, because a completed run has its own notification. The batch's one ledger
348
- // flush rides the same read for the same reason (joshuafolkken/kit#2492, `run-carry-flush.ts`), and
349
- // runs last: it waits out a pull request's CI, so a flush cut short must find the record already gone
350
- // and the stop already announced. The closed-issue stash report (joshuafolkken/kit#2505,
351
- // `run-carry-stash.ts`) is once per invocation on the same read, and goes first because it is quick.
346
+ // reason and stays silent, because a completed run has its own notification. The closed-issue stash
347
+ // report (joshuafolkken/kit#2505, `run-carry-stash.ts`) is once per invocation on the same read.
348
+ //
349
+ // **No ledger flush rides it any more** (joshuafolkken/kit#2919): each lane merges its own ledger
350
+ // lines with its own pull request, so there is nothing left in the primary checkout to flush.
352
351
  async function report_end(read: CarryRead): Promise<void> {
353
352
  if (read.kind === 'none') return
354
353
 
355
354
  await run_carry_stash.report_orphans()
356
- await run_carry_flush.flush_ledger()
357
355
  }
358
356
 
359
357
  async function finish(