@joshuafolkken/kit 1.1035.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 (118) hide show
  1. package/.claude/skills/verify-ui/SKILL.md +1 -1
  2. package/.claude/skills/workflow-commands/SKILL.md +2 -2
  3. package/.claude/skills/workflow-commands/background-commands.md +1 -1
  4. package/.claude/skills/workflow-commands/backlogrun-child.md +2 -2
  5. package/.claude/skills/workflow-commands/backlogrun-lanes.md +1 -1
  6. package/.claude/skills/workflow-commands/backlogrun-park.md +6 -0
  7. package/.claude/skills/workflow-commands/backlogrun-progress.md +30 -36
  8. package/.claude/skills/workflow-commands/backlogrun-steps.md +16 -16
  9. package/.claude/skills/workflow-commands/chain-rule.md +2 -2
  10. package/.claude/skills/workflow-commands/followup-reference.md +2 -2
  11. package/.claude/skills/workflow-commands/issue-comments.md +1 -1
  12. package/.claude/skills/workflow-commands/observation-filing.md +41 -86
  13. package/.claude/skills/workflow-commands/pre-gate-cut.md +33 -36
  14. package/.claude/skills/workflow-commands/working-tree-hold.md +2 -2
  15. package/.github/workflows/auto-tag.yml +21 -0
  16. package/.github/workflows/publish.yml +9 -10
  17. package/dist/hooks/batch-guard.js +4 -4
  18. package/dist/hooks/{chunk-HU66PWWL.js → chunk-5BK3K7V4.js} +89 -41
  19. package/dist/hooks/{chunk-GLBDBJOK.js → chunk-7VPDUBRN.js} +49 -151
  20. package/dist/hooks/{chunk-GJT4BNVE.js → chunk-DMQN45FN.js} +3 -3
  21. package/dist/hooks/{chunk-D67KVGGY.js → chunk-EJDX2TOB.js} +13 -3
  22. package/dist/hooks/{chunk-KG6PZD72.js → chunk-GQ7KP45M.js} +2 -2
  23. package/dist/hooks/{chunk-Y27M6A7X.js → chunk-HJIISEAC.js} +4 -4
  24. package/dist/hooks/{chunk-XPLP27WA.js → chunk-NAS4C7GP.js} +4 -26
  25. package/dist/hooks/{chunk-ZXNE6NY3.js → chunk-NJQOBE6L.js} +2 -2
  26. package/dist/hooks/{chunk-EIFCAJRB.js → chunk-RLDX5SOC.js} +1184 -946
  27. package/dist/hooks/{chunk-HOYJU5RM.js → chunk-WQ4OSBSS.js} +10 -3
  28. package/dist/hooks/codex-hook-adapter.js +10 -10
  29. package/dist/hooks/duplicate-read-guard.js +5 -5
  30. package/dist/hooks/format-edited.js +15 -34
  31. package/dist/hooks/investigation-guard.js +5 -5
  32. package/dist/hooks/pretool-guard.js +23 -16
  33. package/dist/hooks/stop-guard.js +13 -188
  34. package/dist/josh.js +4 -106
  35. package/dist/version/index.js +381 -9
  36. package/package.json +6 -6
  37. package/prompts/collaboration-workflow/file-edits.md +6 -53
  38. package/prompts/collaboration-workflow/residency.md +1 -1
  39. package/prompts/collaboration-workflow/rule-delivery.md +4 -35
  40. package/prompts/collaboration-workflow/shell-body.md +7 -9
  41. package/prompts/collaboration-workflow/turn-batching.md +21 -37
  42. package/prompts/collaboration-workflow/wip-cap.md +15 -18
  43. package/prompts/sonar-hotspot-handling.md +1 -1
  44. package/scripts/adopt/adopt-toolkits.ts +2 -0
  45. package/scripts/auto-ok/auto-ok-cli.ts +9 -9
  46. package/scripts/backlog/backlog-next.ts +24 -8
  47. package/scripts/backlog/backlog-plan-cli.ts +5 -1
  48. package/scripts/backlog/backlog-pool.ts +31 -11
  49. package/scripts/backlog/backlog-rank.ts +106 -0
  50. package/scripts/backlog/backlog-waves.ts +42 -29
  51. package/scripts/build/build-hooks.ts +7 -2
  52. package/scripts/delegation/delegation-policy.ts +11 -0
  53. package/scripts/document/document-byte-budget.ts +5 -5
  54. package/scripts/document/entry-read-budget.ts +5 -4
  55. package/scripts/epic/epic-lane-offer.ts +11 -22
  56. package/scripts/epic/epic-rank.ts +37 -0
  57. package/scripts/epic/epic-solo.ts +29 -28
  58. package/scripts/git/git-command.ts +1 -1
  59. package/scripts/git/git-execa-error.ts +6 -1
  60. package/scripts/git/git-next-issues.ts +34 -5
  61. package/scripts/git/git-preflight.ts +22 -4
  62. package/scripts/git/git-push-transport.ts +12 -69
  63. package/scripts/git/git-spawn.ts +79 -3
  64. package/scripts/git/git-ssh-keepalive.ts +74 -0
  65. package/scripts/git/gone-branch.ts +3 -2
  66. package/scripts/git/issue-labels.ts +12 -0
  67. package/scripts/git/issue-rank.ts +89 -0
  68. package/scripts/hooks/format-edited-cli.ts +15 -0
  69. package/scripts/hooks/format-edited-file.ts +2 -9
  70. package/scripts/hooks/pretool-guard-cli.ts +21 -0
  71. package/scripts/hooks/pretool-guard.ts +14 -15
  72. package/scripts/init/start-exec.ts +7 -1
  73. package/scripts/init/start-plan.ts +18 -5
  74. package/scripts/init/start-setup-pr.ts +46 -19
  75. package/scripts/init/start-steps.ts +41 -9
  76. package/scripts/init/start.ts +40 -10
  77. package/scripts/issue/issue-cite.ts +19 -1
  78. package/scripts/josh/josh-command-map.ts +0 -102
  79. package/scripts/josh/josh-commands-development.ts +2 -2
  80. package/scripts/josh/josh-commands-project.ts +1 -1
  81. package/scripts/lane/lane-child-marker.ts +16 -2
  82. package/scripts/lane/lane-cli.ts +32 -14
  83. package/scripts/lane/lane-report.ts +10 -4
  84. package/scripts/propagate/propagate-plan.ts +185 -0
  85. package/scripts/propagate/propagate-run.ts +5 -1
  86. package/scripts/propagate/propagate-steps.ts +16 -1
  87. package/scripts/propagate/propagate-targets.ts +13 -2
  88. package/scripts/propagate/propagate.ts +60 -42
  89. package/scripts/registry-migration/migrate-logic.ts +1 -0
  90. package/scripts/release/github-release.ts +1 -1
  91. package/scripts/review/review-attest.ts +44 -0
  92. package/scripts/review/review-brief-cli.ts +49 -2
  93. package/scripts/review/review-brief.ts +46 -1
  94. package/scripts/review/review-stamps.ts +7 -0
  95. package/scripts/rules/implementation-cut.ts +1 -1
  96. package/scripts/rules/issue-fold.ts +1 -2
  97. package/scripts/rules/lane-carry-conflict.ts +1 -2
  98. package/scripts/rules/lane-park.ts +1 -2
  99. package/scripts/rules/pre-gate-cut.ts +2 -2
  100. package/scripts/run/run-headless.ts +22 -6
  101. package/scripts/run/run-parent-cut-hook.ts +144 -0
  102. package/scripts/run/run-progress-read.ts +2 -1
  103. package/scripts/run/run-progress.ts +21 -8
  104. package/scripts/run/run-ship-cli.ts +6 -74
  105. package/scripts/run/run-ship-preflight.ts +87 -0
  106. package/scripts/run/run-ship-review-steps.ts +5 -26
  107. package/scripts/run/run-ship-review.ts +9 -0
  108. package/scripts/run/run-ship-scoped.ts +44 -0
  109. package/scripts/run/run-ship-stage.ts +5 -1
  110. package/scripts/run/run-ship-steps.ts +105 -0
  111. package/scripts/run/run-ship.ts +2 -0
  112. package/scripts/run/run-wake-cli.ts +2 -2
  113. package/scripts/run/run-wake-describe.ts +19 -1
  114. package/scripts/run/run-watcher-hook.ts +1 -1
  115. package/scripts/time-runtime/time-bundle-call.ts +2 -2
  116. package/scripts/version/npm-registry.ts +97 -0
  117. package/scripts/version/version-commands.ts +5 -4
  118. package/scripts/version/version-remote.ts +26 -12
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  lane_child_marker,
3
3
  run_issue_number
4
- } from "./chunk-HOYJU5RM.js";
4
+ } from "./chunk-WQ4OSBSS.js";
5
5
  import {
6
6
  PLATFORM_TEMP_ROOT,
7
7
  cost_blocks,
@@ -13,7 +13,7 @@ import {
13
13
  time_density_hook,
14
14
  time_shell,
15
15
  time_transcript_line
16
- } from "./chunk-GLBDBJOK.js";
16
+ } from "./chunk-7VPDUBRN.js";
17
17
  import {
18
18
  ENV_FILE_NAME,
19
19
  LANE_SEAT_KEY,
@@ -153,7 +153,7 @@ var git_utilities = {
153
153
  };
154
154
 
155
155
  // scripts/git/git-push-transport.ts
156
- import { execa } from "execa";
156
+ import { execa as execa2 } from "execa";
157
157
 
158
158
  // scripts/git/git-execa-error.ts
159
159
  function get_exit_code(error) {
@@ -166,25 +166,20 @@ function has_timed_out(error) {
166
166
  if (typeof error !== "object" || error === null || !("timedOut" in error)) return false;
167
167
  return error.timedOut === true;
168
168
  }
169
+ var TIMEOUT_EXIT_CODE = "timeout";
169
170
  function create_spawn_error(command, exit_code) {
170
171
  const exit_code_string = exit_code === void 0 ? "unknown" : String(exit_code);
171
172
  const error_message = `git ${command} exited with code ${exit_code_string}`;
172
173
  return new Error(error_message, { cause: { exit_code: exit_code_string } });
173
174
  }
174
175
 
175
- // scripts/git/git-push-transport.ts
176
- var MS_PER_SECOND = 1e3;
177
- var PUSH_TIMEOUT_SECONDS = 120;
178
- var PUSH_TIMEOUT_MS = PUSH_TIMEOUT_SECONDS * MS_PER_SECOND;
179
- var PUSH_TIMEOUT_LABEL = `${String(PUSH_TIMEOUT_SECONDS)}s`;
180
- var PUSH_TIMEOUT_MESSAGE = "git push timed out";
181
- var PUSH_TIMEOUT_EXIT_CODE = "timeout";
176
+ // scripts/git/git-ssh-keepalive.ts
177
+ import { execa } from "execa";
182
178
  var SSH_KEEPALIVE_OPTIONS = "-o ServerAliveInterval=15 -o ServerAliveCountMax=3";
183
179
  var SSH_COMMAND_VARIABLE = "GIT_SSH_COMMAND";
184
180
  var SSH_LEGACY_VARIABLE = "GIT_SSH";
185
181
  var KEEPALIVE_SSH_COMMAND = `ssh ${SSH_KEEPALIVE_OPTIONS}`;
186
182
  var SSH_COMMAND_CONFIG_KEY = "core.sshCommand";
187
- var PUSH_SUBCOMMAND = "push";
188
183
  function to_environment_value(name) {
189
184
  return process.env[name]?.trim() ?? "";
190
185
  }
@@ -202,19 +197,33 @@ async function has_configured_ssh_command() {
202
197
  return false;
203
198
  }
204
199
  }
205
- async function to_push_options() {
200
+ async function to_environment() {
206
201
  const should_keep_alive = !await has_configured_ssh_command();
202
+ return should_keep_alive ? { [SSH_COMMAND_VARIABLE]: KEEPALIVE_SSH_COMMAND } : {};
203
+ }
204
+ var git_ssh_keepalive = {
205
+ to_environment
206
+ };
207
+
208
+ // scripts/git/git-push-transport.ts
209
+ var MS_PER_SECOND = 1e3;
210
+ var PUSH_TIMEOUT_SECONDS = 120;
211
+ var PUSH_TIMEOUT_MS = PUSH_TIMEOUT_SECONDS * MS_PER_SECOND;
212
+ var PUSH_TIMEOUT_LABEL = `${String(PUSH_TIMEOUT_SECONDS)}s`;
213
+ var PUSH_TIMEOUT_MESSAGE = "git push timed out";
214
+ var PUSH_SUBCOMMAND = "push";
215
+ async function to_push_options() {
207
216
  return {
208
217
  stdio: "inherit",
209
218
  timeout: PUSH_TIMEOUT_MS,
210
- env: should_keep_alive ? { [SSH_COMMAND_VARIABLE]: KEEPALIVE_SSH_COMMAND } : {}
219
+ env: await git_ssh_keepalive.to_environment()
211
220
  };
212
221
  }
213
222
  async function did_push_time_out(arguments_list) {
214
223
  const git_command_bin = git_utilities.get_git_command_for_spawn();
215
224
  const options = await to_push_options();
216
225
  try {
217
- await execa(git_command_bin, [PUSH_SUBCOMMAND, ...arguments_list], options);
226
+ await execa2(git_command_bin, [PUSH_SUBCOMMAND, ...arguments_list], options);
218
227
  return false;
219
228
  } catch (error) {
220
229
  if (has_timed_out(error)) return true;
@@ -229,7 +238,7 @@ function to_retry_notice(arguments_list) {
229
238
  }
230
239
  function create_timeout_error(arguments_list) {
231
240
  const message = `${PUSH_TIMEOUT_MESSAGE} twice (${PUSH_TIMEOUT_LABEL} each). Re-run it by hand: ${to_manual_command(arguments_list)}`;
232
- return new Error(message, { cause: { exit_code: PUSH_TIMEOUT_EXIT_CODE } });
241
+ return new Error(message, { cause: { exit_code: TIMEOUT_EXIT_CODE } });
233
242
  }
234
243
  async function push(arguments_list) {
235
244
  if (!await did_push_time_out(arguments_list)) return;
@@ -242,23 +251,56 @@ var git_push_transport = {
242
251
  };
243
252
 
244
253
  // scripts/git/git-spawn.ts
245
- import { execa as execa2 } from "execa";
254
+ import { execa as execa3 } from "execa";
246
255
  async function read(arguments_) {
247
256
  const git_cmd = git_utilities.get_git_command_for_spawn();
248
- const { stdout } = await execa2(git_cmd, arguments_);
257
+ const { stdout } = await execa3(git_cmd, arguments_);
249
258
  return stdout.trimEnd();
250
259
  }
260
+ var REMOTE_TIMEOUT_MS = PUSH_TIMEOUT_MS;
261
+ var MS_PER_SECOND2 = 1e3;
262
+ function to_remote_error(command, error, timeout_ms) {
263
+ if (!has_timed_out(error)) return create_spawn_error(command, get_exit_code(error));
264
+ const seconds = String(Math.round(timeout_ms / MS_PER_SECOND2));
265
+ return new Error(`git ${command} timed out after ${seconds}s`, {
266
+ cause: { exit_code: TIMEOUT_EXIT_CODE }
267
+ });
268
+ }
269
+ async function spawn_remote(arguments_, options) {
270
+ const git_cmd = git_utilities.get_git_command_for_spawn();
271
+ try {
272
+ const { stdout } = await execa3(git_cmd, arguments_, options);
273
+ return typeof stdout === "string" ? stdout.trimEnd() : "";
274
+ } catch (error) {
275
+ throw to_remote_error(arguments_[0] ?? "", error, options.timeout);
276
+ }
277
+ }
278
+ async function read_remote(arguments_, timeout_ms = REMOTE_TIMEOUT_MS) {
279
+ return await spawn_remote(arguments_, {
280
+ timeout: timeout_ms,
281
+ env: await git_ssh_keepalive.to_environment()
282
+ });
283
+ }
284
+ async function with_output_remote(command, arguments_list) {
285
+ await spawn_remote([command, ...arguments_list], {
286
+ stdio: "inherit",
287
+ timeout: REMOTE_TIMEOUT_MS,
288
+ env: await git_ssh_keepalive.to_environment()
289
+ });
290
+ }
251
291
  async function with_output(command, arguments_list) {
252
292
  const git_command_bin = git_utilities.get_git_command_for_spawn();
253
293
  try {
254
- await execa2(git_command_bin, [command, ...arguments_list], { stdio: "inherit" });
294
+ await execa3(git_command_bin, [command, ...arguments_list], { stdio: "inherit" });
255
295
  } catch (error) {
256
296
  throw create_spawn_error(command, get_exit_code(error));
257
297
  }
258
298
  }
259
299
  var git_spawn = {
260
300
  read,
261
- with_output
301
+ read_remote,
302
+ with_output,
303
+ with_output_remote
262
304
  };
263
305
 
264
306
  // scripts/git/git-command.ts
@@ -433,7 +475,7 @@ async function push2() {
433
475
  }
434
476
  }
435
477
  async function pull_fast_forward() {
436
- await git_spawn.with_output("pull", ["--ff-only"]);
478
+ await git_spawn.with_output_remote("pull", ["--ff-only"]);
437
479
  }
438
480
  var SHORT_NAME_FORMAT = "--format=%(refname:short)";
439
481
  var REMOTES_FLAG = "--remotes";
@@ -840,18 +882,18 @@ function trimmed(value) {
840
882
  const result = value?.trim();
841
883
  return result === void 0 || result.length === EMPTY_LENGTH ? void 0 : result;
842
884
  }
843
- function override_value(role, field, environment) {
885
+ function override_value(role, field, environment2) {
844
886
  const key = ENV_KEYS[role][field];
845
- const value = trimmed(environment[key]);
887
+ const value = trimmed(environment2[key]);
846
888
  if (value !== void 0) return { key, value };
847
889
  if (role !== WORKER) return { key, value: void 0 };
848
890
  const legacy = LEGACY_WORKER_KEYS[field];
849
- const legacy_value = trimmed(environment[legacy]);
891
+ const legacy_value = trimmed(environment2[legacy]);
850
892
  return legacy_value === void 0 ? { key, value: void 0 } : { key: legacy, value: legacy_value };
851
893
  }
852
- function model_override(role, environment, provider) {
894
+ function model_override(role, environment2, provider) {
853
895
  const key = ENV_KEYS[role].model;
854
- return provider === OPENAI_PROVIDER ? { key, value: void 0 } : override_value(role, "model", environment);
896
+ return provider === OPENAI_PROVIDER ? { key, value: void 0 } : override_value(role, "model", environment2);
855
897
  }
856
898
  function rejection(key, value, expected) {
857
899
  return { kind: "rejected", note: `${key}=${value} ${expected}, so the agent was not started` };
@@ -860,35 +902,35 @@ function resolved_effort(override, fallback) {
860
902
  const parsed = EFFORT_SCHEMA.safeParse(override.value ?? fallback);
861
903
  return parsed.success ? { kind: "effort", effort: parsed.data } : rejection(override.key, override.value ?? "", "is not an allowed effort");
862
904
  }
863
- function has_session(environment, keys) {
864
- return keys.some((key) => trimmed(environment[key]) !== void 0);
905
+ function has_session(environment2, keys) {
906
+ return keys.some((key) => trimmed(environment2[key]) !== void 0);
865
907
  }
866
908
  function session_rejection(is_conflicting) {
867
909
  const note = is_conflicting ? "both Codex and Claude Code sessions were detected" : "no Codex or Claude Code session was detected";
868
910
  return { kind: "rejected", note };
869
911
  }
870
912
  var HANDED_PROVIDER_KEY = "JOSH_AGENT_PROVIDER";
871
- function handed_provider(environment) {
872
- const value = trimmed(environment[HANDED_PROVIDER_KEY]);
913
+ function handed_provider(environment2) {
914
+ const value = trimmed(environment2[HANDED_PROVIDER_KEY]);
873
915
  if (value === void 0) return session_rejection(false);
874
916
  const parsed = PROVIDER_SCHEMA.safeParse(value);
875
917
  return parsed.success ? { kind: "provider", provider: parsed.data } : rejection(HANDED_PROVIDER_KEY, value, "is not an allowed provider");
876
918
  }
877
- function detected_provider(environment) {
878
- const has_codex = has_session(environment, [CODEX_SESSION_KEY]);
879
- const has_claude = has_session(environment, agent_session_environment.PARENT_SESSION_KEYS);
919
+ function detected_provider(environment2) {
920
+ const has_codex = has_session(environment2, [CODEX_SESSION_KEY]);
921
+ const has_claude = has_session(environment2, agent_session_environment.PARENT_SESSION_KEYS);
880
922
  if (has_codex === has_claude) return has_codex ? session_rejection(true) : void 0;
881
923
  return { kind: "provider", provider: has_codex ? OPENAI_PROVIDER : ANTHROPIC_PROVIDER };
882
924
  }
883
- function resolve_provider(environment = process.env) {
884
- return detected_provider(environment) ?? handed_provider(environment);
925
+ function resolve_provider(environment2 = process.env) {
926
+ return detected_provider(environment2) ?? handed_provider(environment2);
885
927
  }
886
- function has_completion_callback(environment = process.env) {
887
- const selected = resolve_provider(environment);
928
+ function has_completion_callback(environment2 = process.env) {
929
+ const selected = resolve_provider(environment2);
888
930
  return selected.kind !== "provider" || selected.provider !== OPENAI_PROVIDER;
889
931
  }
890
- function handoff_environment(environment = process.env) {
891
- const selected = resolve_provider(environment);
932
+ function handoff_environment(environment2 = process.env) {
933
+ const selected = resolve_provider(environment2);
892
934
  return selected.kind === "provider" ? { [HANDED_PROVIDER_KEY]: selected.provider } : {};
893
935
  }
894
936
  function validate(profile, model_key) {
@@ -902,19 +944,19 @@ function phase_effort(role, phase, fallback) {
902
944
  if (!parsed.success) return fallback;
903
945
  return PHASE_EFFORT[role]?.[parsed.data] ?? fallback;
904
946
  }
905
- function overridden_effort(profile, phase, environment) {
906
- const parsed = EFFORT_SCHEMA.safeParse(override_value(profile.role, "effort", environment).value);
947
+ function overridden_effort(profile, phase, environment2) {
948
+ const parsed = EFFORT_SCHEMA.safeParse(override_value(profile.role, "effort", environment2).value);
907
949
  return parsed.success ? parsed.data : phase_effort(profile.role, phase, profile.effort);
908
950
  }
909
- function with_phase_effort(profile, phase, environment = process.env) {
910
- return { ...profile, effort: overridden_effort(profile, phase, environment) };
951
+ function with_phase_effort(profile, phase, environment2 = process.env) {
952
+ return { ...profile, effort: overridden_effort(profile, phase, environment2) };
911
953
  }
912
- function resolve(role, environment = process.env, phase) {
913
- const selected = resolve_provider(environment);
954
+ function resolve(role, environment2 = process.env, phase) {
955
+ const selected = resolve_provider(environment2);
914
956
  if (selected.kind === "rejected") return selected;
915
957
  const defaults = PROVIDER_PROFILES[selected.provider][role];
916
- const model = model_override(role, environment, selected.provider);
917
- const effort = override_value(role, "effort", environment);
958
+ const model = model_override(role, environment2, selected.provider);
959
+ const effort = override_value(role, "effort", environment2);
918
960
  const resolved = resolved_effort(effort, phase_effort(role, phase, defaults.effort));
919
961
  if (resolved.kind === "rejected") return resolved;
920
962
  const profile = {
@@ -1312,10 +1354,10 @@ function load_handoff(handoff_path, max_bytes) {
1312
1354
  }
1313
1355
  return { kind: "ok", handoff };
1314
1356
  }
1315
- function describe_list(label, items) {
1316
- if (items.length === 0) return `${label}: ${EMPTY_LIST_NOTE}`;
1357
+ function describe_list(label2, items) {
1358
+ if (items.length === 0) return `${label2}: ${EMPTY_LIST_NOTE}`;
1317
1359
  const bullets = items.map((item) => ` - ${item}`).join("\n");
1318
- return `${label}:
1360
+ return `${label2}:
1319
1361
  ${bullets}`;
1320
1362
  }
1321
1363
  function describe_handoff(handoff) {
@@ -1671,6 +1713,7 @@ var IN_PROGRESS_LABEL = "in-progress";
1671
1713
  var NEEDS_DECISION_LABEL = "needs-decision";
1672
1714
  var RUN_SOLO_LABEL = "run:solo";
1673
1715
  var RUN_LANE_LABEL = "run:lane";
1716
+ var PRIORITY_HIGH_LABEL = "priority:high";
1674
1717
  var NEEDS_HUMAN_REVIEW_LABEL = "needs-human-review";
1675
1718
  var ALREADY_DONE_LABEL = "already-done";
1676
1719
  var INTERRUPT_ROUTE_LABEL = "route:interrupt";
@@ -1740,6 +1783,11 @@ var WORKFLOW_LABELS = [
1740
1783
  color: "0e8a16",
1741
1784
  description: "Judged safe to run beside others in a backlogrun"
1742
1785
  },
1786
+ {
1787
+ name: PRIORITY_HIGH_LABEL,
1788
+ color: "e99695",
1789
+ description: "Offered first in a backlogrun; only a person removes it"
1790
+ },
1743
1791
  ...FILING_ROUTE_LABELS,
1744
1792
  ...DEPTH_LABELS
1745
1793
  ];
@@ -1766,11 +1814,11 @@ var REPOSITORY_LABELS = [
1766
1814
  ...RELEASE_CLASSIFICATION_LABELS
1767
1815
  ];
1768
1816
  function has_any_label(labels, wanted) {
1769
- return (labels ?? []).some((label) => wanted.has(label.name.toLowerCase()));
1817
+ return (labels ?? []).some((label2) => wanted.has(label2.name.toLowerCase()));
1770
1818
  }
1771
1819
  function label_name_of(labels, wanted) {
1772
1820
  const target = wanted.toLowerCase();
1773
- return labels.find((label) => label.toLowerCase() === target);
1821
+ return labels.find((label2) => label2.toLowerCase() === target);
1774
1822
  }
1775
1823
  function has_label_name(labels, wanted) {
1776
1824
  return label_name_of(labels, wanted) !== void 0;
@@ -2003,14 +2051,14 @@ function main_worktree(repository_path) {
2003
2051
  if (path.basename(git_directory) !== GIT_DIRECTORY) return repository_path;
2004
2052
  return path.dirname(git_directory);
2005
2053
  }
2006
- function discover_repositories(repository_path, environment = process.env) {
2054
+ function discover_repositories(repository_path, environment2 = process.env) {
2007
2055
  const main2 = main_worktree(repository_path);
2008
2056
  const current_owner2 = resolve_current_owner(main2);
2009
2057
  if (current_owner2 === void 0) return /* @__PURE__ */ new Map();
2010
2058
  return repo_map_logic.build_repository_map(
2011
2059
  scan_siblings(path.dirname(main2)),
2012
2060
  current_owner2,
2013
- environment[OVERRIDE_ENV_KEY]
2061
+ environment2[OVERRIDE_ENV_KEY]
2014
2062
  );
2015
2063
  }
2016
2064
  var repo_discovery = {
@@ -2038,36 +2086,97 @@ function current_owner(repository_path = process.cwd()) {
2038
2086
  }
2039
2087
  var repo_party = { FIRST_PARTY, THIRD_PARTY, UNKNOWN, classify: classify3, current_owner, target_owner };
2040
2088
 
2041
- // scripts/lane/lane-registry.ts
2042
- import { existsSync as existsSync2 } from "node:fs";
2043
- import path3 from "node:path";
2044
-
2045
- // scripts/git/git-ls-remote.ts
2046
- var LS_REMOTE_COMMAND = "ls-remote";
2047
- var HEADS_FLAG = "--heads";
2048
- var ORIGIN_REMOTE = "origin";
2049
- var HEADS_REF_PREFIX = "refs/heads/";
2050
- function ls_remote_branch_arguments(branch_name) {
2051
- return [LS_REMOTE_COMMAND, HEADS_FLAG, ORIGIN_REMOTE, `${HEADS_REF_PREFIX}${branch_name}`];
2089
+ // scripts/lane/lane-reap.ts
2090
+ import { execFileSync as execFileSync2 } from "node:child_process";
2091
+ var PGREP_COMMAND = "pgrep";
2092
+ var MATCH_FULL_COMMAND_LINE = "-f";
2093
+ var CHILDREN_OF_FLAG = "-P";
2094
+ var PS_COMMAND = "/bin/ps";
2095
+ var PARENT_FORMAT = ["-o", "ppid=", "-p"];
2096
+ var TERMINATE_SIGNAL = "SIGTERM";
2097
+ var PROBE_TIMEOUT_MS = 2e3;
2098
+ var LOWEST_CHILD_PID = 2;
2099
+ function parse_pids(printed) {
2100
+ return printed.split("\n").map((line) => Number(line.trim())).filter((pid) => Number.isSafeInteger(pid) && pid >= LOWEST_CHILD_PID);
2052
2101
  }
2053
-
2054
- // scripts/git/repository-lock.ts
2055
- import { randomUUID as randomUUID2 } from "node:crypto";
2056
- import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
2057
- import { setTimeout as sleep } from "node:timers/promises";
2102
+ function run_probe(command, args) {
2103
+ try {
2104
+ return execFileSync2(command, [...args], {
2105
+ encoding: "utf8",
2106
+ timeout: PROBE_TIMEOUT_MS,
2107
+ stdio: ["ignore", "pipe", "ignore"]
2108
+ });
2109
+ } catch {
2110
+ return "";
2111
+ }
2112
+ }
2113
+ function parent_of(pid) {
2114
+ return parse_pids(run_probe(PS_COMMAND, [...PARENT_FORMAT, String(pid)]))[0];
2115
+ }
2116
+ function terminate(pid) {
2117
+ try {
2118
+ process.kill(pid, TERMINATE_SIGNAL);
2119
+ return true;
2120
+ } catch {
2121
+ return false;
2122
+ }
2123
+ }
2124
+ var SYSTEM_PROBES = {
2125
+ matching: (pattern) => parse_pids(run_probe(PGREP_COMMAND, [MATCH_FULL_COMMAND_LINE, pattern])),
2126
+ children_of: (pid) => parse_pids(run_probe(PGREP_COMMAND, [CHILDREN_OF_FLAG, String(pid)])),
2127
+ parent_of,
2128
+ terminate
2129
+ };
2130
+ function ancestry_of(pid, probes) {
2131
+ const ancestry = /* @__PURE__ */ new Set([pid]);
2132
+ let current = probes.parent_of(pid);
2133
+ while (current !== void 0 && !ancestry.has(current)) {
2134
+ ancestry.add(current);
2135
+ current = probes.parent_of(current);
2136
+ }
2137
+ return ancestry;
2138
+ }
2139
+ function own_ancestry(probes = SYSTEM_PROBES) {
2140
+ return ancestry_of(process.pid, probes);
2141
+ }
2142
+ function tree_of(roots, probes) {
2143
+ const tree = [];
2144
+ const pending = [...roots];
2145
+ let next = pending.shift();
2146
+ while (next !== void 0) {
2147
+ if (!tree.includes(next)) {
2148
+ tree.push(next);
2149
+ pending.push(...probes.children_of(next));
2150
+ }
2151
+ next = pending.shift();
2152
+ }
2153
+ return tree;
2154
+ }
2155
+ function reap_child(issue, probes = SYSTEM_PROBES) {
2156
+ const own = own_ancestry(probes);
2157
+ const pattern = lane_child_invocation.process_pattern(issue);
2158
+ const roots = probes.matching(pattern).filter((pid) => !own.has(pid));
2159
+ const doomed = tree_of(roots, probes).filter((pid) => !own.has(pid));
2160
+ return doomed.filter((pid) => probes.terminate(pid));
2161
+ }
2162
+ var lane_reap = {
2163
+ own_ancestry,
2164
+ parse_pids,
2165
+ reap_child
2166
+ };
2058
2167
 
2059
2168
  // scripts/josh/process-identity.ts
2060
- import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
2169
+ import { execFileSync as execFileSync3, spawnSync } from "node:child_process";
2061
2170
  import { randomUUID } from "node:crypto";
2062
2171
  import { readFileSync as readFileSync3, rmSync } from "node:fs";
2063
2172
  import { createServer } from "node:net";
2064
2173
  import path2 from "node:path";
2065
- var PS_COMMAND = "/bin/ps";
2174
+ var PS_COMMAND2 = "/bin/ps";
2066
2175
  var FORMAT_FLAG = "-o";
2067
2176
  var START_TIME_FORMAT = "lstart=";
2068
2177
  var PID_FLAG = "-p";
2069
2178
  var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
2070
- var PROBE_TIMEOUT_MS = 2e3;
2179
+ var PROBE_TIMEOUT_MS2 = 2e3;
2071
2180
  var LIVENESS_SIGNAL = 0;
2072
2181
  var LOWEST_REAL_PID = 1;
2073
2182
  var PROC_START_FIELD_INDEX = 19;
@@ -2101,12 +2210,12 @@ function read_proc_start(pid) {
2101
2210
  }
2102
2211
  function read_ps_start(pid) {
2103
2212
  try {
2104
- const printed = execFileSync2(
2105
- PS_COMMAND,
2213
+ const printed = execFileSync3(
2214
+ PS_COMMAND2,
2106
2215
  [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
2107
2216
  {
2108
2217
  encoding: "utf8",
2109
- timeout: PROBE_TIMEOUT_MS,
2218
+ timeout: PROBE_TIMEOUT_MS2,
2110
2219
  stdio: ["ignore", "pipe", "ignore"],
2111
2220
  env: PROBE_ENVIRONMENT
2112
2221
  }
@@ -2184,7 +2293,7 @@ function beacon_target(token) {
2184
2293
  function is_live_beacon(target) {
2185
2294
  return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
2186
2295
  stdio: "ignore",
2187
- timeout: PROBE_TIMEOUT_MS
2296
+ timeout: PROBE_TIMEOUT_MS2
2188
2297
  }).status === 0;
2189
2298
  }
2190
2299
  function has_matching_start(pid, recorded_start, read2) {
@@ -2224,984 +2333,1002 @@ var process_identity = {
2224
2333
  resolve_own_start
2225
2334
  };
2226
2335
 
2227
- // scripts/git/git-common-directory.ts
2228
- import { execFileSync as execFileSync3 } from "node:child_process";
2229
- var OWN_GIT_DIRECTORY = 0;
2230
- var COMMON_GIT_DIRECTORY = 1;
2231
- var PROBE_TIMEOUT_MS2 = 2e3;
2232
- function read_directories(cwd) {
2233
- const git = git_utilities.get_git_command_for_spawn();
2234
- const output = execFileSync3(git, [...git_command.GIT_DIRECTORY_ARGUMENTS], {
2235
- cwd,
2236
- encoding: "utf8",
2237
- stdio: ["ignore", "pipe", "ignore"],
2238
- timeout: PROBE_TIMEOUT_MS2
2239
- });
2240
- return output.trim().split("\n");
2336
+ // scripts/run/run-carry.ts
2337
+ import { z as z5 } from "zod";
2338
+
2339
+ // scripts/run/run-carry-streak.ts
2340
+ var NO_INCREMENT = 0;
2341
+ var OUTAGE_FOLD_WINDOW_MS = 12e4;
2342
+ function has_increment(value) {
2343
+ return (value ?? NO_INCREMENT) > NO_INCREMENT;
2241
2344
  }
2242
- function select_linked(directories) {
2243
- const own = directories[OWN_GIT_DIRECTORY];
2244
- const common = directories[COMMON_GIT_DIRECTORY];
2245
- return own !== void 0 && common !== void 0 && own !== common ? common : void 0;
2345
+ function next_failures(carry, change) {
2346
+ if (has_increment(change.merged)) return NO_INCREMENT;
2347
+ return carry.failures + (change.failures ?? NO_INCREMENT);
2246
2348
  }
2247
- function resolve2(cwd) {
2248
- try {
2249
- return select_linked(read_directories(cwd));
2250
- } catch {
2251
- return void 0;
2252
- }
2349
+ function is_within_fold_window(last_outage_at, now) {
2350
+ if (last_outage_at === void 0) return false;
2351
+ const last = Date.parse(last_outage_at);
2352
+ if (Number.isNaN(last)) return false;
2353
+ return now.getTime() - last <= OUTAGE_FOLD_WINDOW_MS;
2253
2354
  }
2254
- function repository(cwd) {
2255
- try {
2256
- return read_directories(cwd)[COMMON_GIT_DIRECTORY];
2257
- } catch {
2258
- return void 0;
2259
- }
2355
+ function folded_outage(carry) {
2356
+ return { outages: carry.outages, last_outage_at: carry.last_outage_at };
2260
2357
  }
2261
- var git_common_directory = { repository, resolve: resolve2, select_linked };
2262
-
2263
- // scripts/git/repository-lock.ts
2264
- var POLL_INTERVAL_MS = 200;
2265
- function lock_path(prefix, cwd = process.cwd()) {
2266
- return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd);
2358
+ function counted_outage(carry, change, now) {
2359
+ const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2360
+ return { outages, last_outage_at: now.toISOString() };
2267
2361
  }
2268
- function parse_owner(raw) {
2269
- if (raw === void 0) return void 0;
2270
- try {
2271
- return JSON.parse(raw);
2272
- } catch {
2273
- return void 0;
2362
+ function next_outage(carry, change, now) {
2363
+ if (has_increment(change.outages)) {
2364
+ return is_within_fold_window(carry.last_outage_at, now) ? folded_outage(carry) : counted_outage(carry, change, now);
2274
2365
  }
2366
+ const is_reachable = has_increment(change.merged) || has_increment(change.failures);
2367
+ return is_reachable ? { outages: NO_INCREMENT, last_outage_at: void 0 } : folded_outage(carry);
2275
2368
  }
2276
- function read_owner(target) {
2277
- return parse_owner(stamp_file.read_stamp_text(target));
2278
- }
2279
- function is_owner_gone(owner) {
2280
- return process_identity.is_same_process(owner.pid, owner.process_start) === false;
2281
- }
2282
- function restore(aside, target) {
2283
- try {
2284
- linkSync(aside, target);
2285
- } catch {
2286
- }
2369
+ function next_state(carry, change, now) {
2370
+ const outage = next_outage(carry, change, now);
2371
+ return {
2372
+ failures: next_failures(carry, change),
2373
+ outages: outage.outages,
2374
+ last_outage_at: outage.last_outage_at
2375
+ };
2287
2376
  }
2288
- function stale_record(target, is_gone2) {
2289
- const raw = stamp_file.read_stamp_text(target);
2290
- const owner = parse_owner(raw);
2291
- return owner !== void 0 && is_gone2(owner) ? raw : void 0;
2377
+ var run_carry_streak = { OUTAGE_FOLD_WINDOW_MS, next_state };
2378
+
2379
+ // scripts/run/run-carry-change.ts
2380
+ var NO_INCREMENT2 = 0;
2381
+ function next_done(carry, done) {
2382
+ if (done === void 0) return carry.done;
2383
+ const current = carry.done ?? [];
2384
+ return current.includes(done) ? current : [...current, done];
2292
2385
  }
2293
- function move_aside(target) {
2294
- const aside = `${target}.${String(process.pid)}.${randomUUID2()}`;
2295
- try {
2296
- renameSync(target, aside);
2297
- return aside;
2298
- } catch {
2299
- return void 0;
2300
- }
2386
+ function next_merged_issues(carry, issue) {
2387
+ if (issue === void 0) return carry.merged_issues;
2388
+ return [...carry.merged_issues ?? [], issue];
2301
2389
  }
2302
- function clear_stale(target, is_gone2 = is_owner_gone) {
2303
- const raw = stale_record(target, is_gone2);
2304
- if (raw === void 0) return;
2305
- const aside = move_aside(target);
2306
- if (aside === void 0) return;
2307
- if (stamp_file.read_stamp_text(aside) !== raw) restore(aside, target);
2308
- rmSync2(aside, { force: true });
2390
+ function is_duplicate_merge(carry, change) {
2391
+ const issue = change.merged_issue;
2392
+ return issue !== void 0 && carry.merged_issues?.includes(issue) === true;
2309
2393
  }
2310
- function claim(target) {
2311
- clear_stale(target);
2312
- return stamp_file.create_stamp(target, process_identity.own_fields());
2394
+ function next_counts(carry, change) {
2395
+ const cuts = change.cuts ?? NO_INCREMENT2;
2396
+ return {
2397
+ merged: carry.merged + (change.merged ?? NO_INCREMENT2),
2398
+ filed: carry.filed + (change.filed ?? NO_INCREMENT2),
2399
+ cuts: carry.cuts + cuts,
2400
+ is_handed_off: cuts > NO_INCREMENT2
2401
+ };
2313
2402
  }
2314
- function release(target) {
2315
- const owner = read_owner(target);
2316
- if (owner !== void 0 && process_identity.is_own_process(owner.pid, owner.process_start)) {
2317
- stamp_file.remove_stamp(target);
2318
- }
2403
+ function apply_change(target, carry, change, now = /* @__PURE__ */ new Date()) {
2404
+ if (is_duplicate_merge(carry, change)) return carry;
2405
+ const streak = run_carry_streak.next_state(carry, change, now);
2406
+ const next = {
2407
+ ...carry,
2408
+ ...next_counts(carry, change),
2409
+ merged_issues: next_merged_issues(carry, change.merged_issue),
2410
+ failures: streak.failures,
2411
+ outages: streak.outages,
2412
+ last_outage_at: streak.last_outage_at,
2413
+ done: next_done(carry, change.done),
2414
+ retrospective: change.retrospective === true || carry.retrospective
2415
+ };
2416
+ stamp_file.write_stamp(target, next);
2417
+ return next;
2319
2418
  }
2320
- async function acquire(target, max_wait_ms) {
2321
- const deadline = Date.now() + max_wait_ms;
2322
- while (!claim(target)) {
2323
- if (Date.now() >= deadline) return false;
2324
- await sleep(POLL_INTERVAL_MS);
2325
- }
2326
- return true;
2419
+ var run_carry_change = { apply_change };
2420
+
2421
+ // scripts/backlog/backlog-budget-cli.ts
2422
+ import { fileURLToPath } from "node:url";
2423
+
2424
+ // scripts/git/changed-paths.ts
2425
+ function to_paths(raw) {
2426
+ return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
2327
2427
  }
2328
- async function with_lock(work, target, max_wait_ms) {
2329
- if (!await acquire(target, max_wait_ms)) return void 0;
2330
- try {
2331
- return await work();
2332
- } finally {
2333
- release(target);
2334
- }
2428
+ async function read_changed_paths(is_staged) {
2429
+ if (is_staged) return to_paths(await git_command.diff_cached_names());
2430
+ const tracked = to_paths(await git_command.diff_main_names());
2431
+ const untracked = to_paths(await git_command.untracked_names());
2432
+ return [...tracked, ...untracked];
2335
2433
  }
2336
- var repository_lock = { clear_stale, lock_path, with_lock };
2434
+ var changed_paths = { read_changed_paths, to_paths };
2337
2435
 
2338
- // scripts/git/git-worktree.ts
2339
- var WORKTREE = "worktree";
2340
- var WORKTREE_LOCK_PREFIX = "josh-worktree-lock-";
2341
- var WORKTREE_LOCK_MAX_WAIT_MS = 12e4;
2342
- async function locked(arguments_) {
2343
- const target = repository_lock.lock_path(WORKTREE_LOCK_PREFIX);
2344
- const output = await repository_lock.with_lock(
2345
- async () => await git_spawn.read(arguments_),
2346
- target,
2347
- WORKTREE_LOCK_MAX_WAIT_MS
2348
- );
2349
- if (output === void 0) {
2350
- throw new Error(
2351
- `git ${arguments_.join(" ")}: timed out waiting for the work-tree lock ${target}`
2352
- );
2353
- }
2354
- return output;
2355
- }
2356
- async function worktree_list() {
2357
- return await git_spawn.read([WORKTREE, "list", PORCELAIN_FLAG]);
2358
- }
2359
- async function worktree_add(directory, branch_name, start_point) {
2360
- const flags = start_point === void 0 ? ["add"] : ["add", "--no-track", "-b", branch_name];
2361
- return await locked([WORKTREE, ...flags, directory, start_point ?? branch_name]);
2362
- }
2363
- async function worktree_add_detached(directory, commit2) {
2364
- return await locked([WORKTREE, "add", "--detach", directory, commit2]);
2436
+ // scripts/josh/path-decision.ts
2437
+ var STAGED_FLAG = "--staged";
2438
+ var JSON_FLAG = "--json";
2439
+ var KNOWN_FLAGS = [STAGED_FLAG, JSON_FLAG];
2440
+ var FAILURE_EXIT_CODE = 1;
2441
+ var MAX_LISTED_PATHS = 5;
2442
+ function has_unknown_flag(argv, known) {
2443
+ return argv.some((argument) => !known.includes(argument));
2365
2444
  }
2366
- async function worktree_remove(directory) {
2367
- return await locked([WORKTREE, "remove", "--force", directory]);
2445
+ function parse_options(argv) {
2446
+ if (has_unknown_flag(argv, KNOWN_FLAGS)) return void 0;
2447
+ return { is_staged: argv.includes(STAGED_FLAG), is_json: argv.includes(JSON_FLAG) };
2368
2448
  }
2369
- async function worktree_prune() {
2370
- return await locked([WORKTREE, "prune"]);
2449
+ function format_path_list(paths) {
2450
+ const listed = paths.slice(0, MAX_LISTED_PATHS).join(", ");
2451
+ if (paths.length <= MAX_LISTED_PATHS) return listed;
2452
+ return `${listed}, +${String(paths.length - MAX_LISTED_PATHS)} more`;
2371
2453
  }
2372
- async function ls_remote_branch(branch_name) {
2373
- return await git_spawn.read(ls_remote_branch_arguments(branch_name));
2454
+ function print_decision(key, answer, reason, is_json) {
2455
+ if (is_json) {
2456
+ console.info(JSON.stringify({ [key]: answer, reason }));
2457
+ return;
2458
+ }
2459
+ console.info(answer);
2460
+ console.error(reason);
2374
2461
  }
2375
- async function branch_delete(branch_name) {
2376
- return await git_spawn.read(["branch", "-D", branch_name]);
2462
+ async function run_path_decision(argv, command) {
2463
+ const options = parse_options(argv);
2464
+ if (options === void 0) {
2465
+ console.error(command.usage);
2466
+ return FAILURE_EXIT_CODE;
2467
+ }
2468
+ const paths = await changed_paths.read_changed_paths(options.is_staged);
2469
+ const answer = command.decide(paths);
2470
+ print_decision(command.key, answer, command.explain(paths, answer), options.is_json);
2471
+ return 0;
2377
2472
  }
2378
- var git_worktree = {
2379
- branch_delete,
2380
- ls_remote_branch,
2381
- worktree_add,
2382
- worktree_add_detached,
2383
- worktree_list,
2384
- worktree_prune,
2385
- worktree_remove
2473
+ var path_decision = {
2474
+ format_path_list,
2475
+ has_unknown_flag,
2476
+ JSON_FLAG,
2477
+ KNOWN_FLAGS,
2478
+ MAX_LISTED_PATHS,
2479
+ parse_options,
2480
+ print_decision,
2481
+ run_path_decision,
2482
+ STAGED_FLAG
2386
2483
  };
2387
2484
 
2388
- // scripts/lane/lane-environment.ts
2389
- var LANE_OUTPUT_KEY = "JOSH_LANE_OUTPUT";
2390
- var LANE_PROVIDER_KEY = "JOSH_LANE_AGENT_PROVIDER";
2391
- var LANE_ROLE_KEY = "JOSH_LANE_AGENT_ROLE";
2392
- var LANE_MODEL_KEY = "JOSH_LANE_AGENT_MODEL";
2393
- var LANE_EFFORT_KEY = "JOSH_LANE_AGENT_EFFORT";
2394
- var ASSIGNMENT_SEPARATOR = "=";
2395
- var EXPORT_PREFIX = "export ";
2396
- var QUOTE_CHARACTERS = /* @__PURE__ */ new Set(['"', "'"]);
2397
- var MIN_QUOTED_LENGTH = 2;
2398
- var FIRST_CHARACTER_END = 1;
2399
- var LAST_CHARACTER_START = -1;
2400
- function strip_export(line) {
2401
- const trimmed2 = line.trimStart();
2402
- return trimmed2.startsWith(EXPORT_PREFIX) ? trimmed2.slice(EXPORT_PREFIX.length).trimStart() : trimmed2;
2485
+ // scripts/lib/cli-flags.ts
2486
+ import { parseArgs } from "node:util";
2487
+ function refuse_unknown_flags(argv, known_flags, command) {
2488
+ const unknown = argv.filter((argument) => !known_flags.includes(argument));
2489
+ if (unknown.length === 0) return void 0;
2490
+ const usage = `Usage: josh ${command} [${known_flags.join("] [")}]`;
2491
+ return `Unknown argument(s): ${unknown.join(" ")}
2492
+ ${usage}`;
2403
2493
  }
2404
- function is_key_line(line, key) {
2405
- const body = strip_export(line);
2406
- if (!body.startsWith(key)) return false;
2407
- return body.slice(key.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
2494
+ function parse_or_undefined(config) {
2495
+ try {
2496
+ return parseArgs(config);
2497
+ } catch {
2498
+ return void 0;
2499
+ }
2408
2500
  }
2409
- function is_seed_line(line) {
2410
- return is_key_line(line, PORT_SEED_KEY);
2501
+ var cli_flags = { refuse_unknown_flags, parse_or_undefined };
2502
+
2503
+ // scripts/backlog/backlog-budget-cli.ts
2504
+ var ARGV_OFFSET = 2;
2505
+ var SUCCESS_EXIT_CODE = 0;
2506
+ var FAILURE_EXIT_CODE2 = 1;
2507
+ var JSON_KEY = "budget";
2508
+ var USAGE = `Usage: josh backlog:budget --answer <${backlog_budget.ANSWERS.join("|")}> --started <ISO-8601> [--active <ISO-8601>, required unless the watch is off] [--merged <count>] [--running <count>] [--max <count>] [--idle <minutes>, 0 to turn the watch off] [--json]`;
2509
+ var OPTIONS = {
2510
+ active: { type: "string" },
2511
+ answer: { type: "string" },
2512
+ idle: { type: "string" },
2513
+ json: { type: "boolean" },
2514
+ max: { type: "string" },
2515
+ merged: { type: "string" },
2516
+ running: { type: "string" },
2517
+ started: { type: "string" }
2518
+ };
2519
+ function read_arguments(argv) {
2520
+ return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values;
2411
2521
  }
2412
- function is_seat_line(line) {
2413
- return is_key_line(line, LANE_SEAT_KEY);
2522
+ function text_of(value) {
2523
+ return typeof value === "string" ? value : void 0;
2414
2524
  }
2415
- function is_output_line(line) {
2416
- return is_key_line(line, LANE_OUTPUT_KEY);
2525
+ function to_answer(raw) {
2526
+ return backlog_budget.ANSWERS.find((answer) => answer === raw);
2417
2527
  }
2418
- function split_lines(content) {
2419
- const trimmed2 = content.trimEnd();
2420
- return trimmed2.length === 0 ? [] : trimmed2.split("\n");
2528
+ function to_time_ms(raw) {
2529
+ if (raw === void 0) return void 0;
2530
+ const parsed = Date.parse(raw);
2531
+ return Number.isNaN(parsed) ? void 0 : parsed;
2421
2532
  }
2422
- function is_quoted(raw) {
2423
- const first = raw.slice(0, FIRST_CHARACTER_END);
2424
- if (!QUOTE_CHARACTERS.has(first)) return false;
2425
- return raw.length >= MIN_QUOTED_LENGTH && raw.endsWith(first);
2533
+ var COUNT_PATTERN = /^\d+$/u;
2534
+ function to_count(raw) {
2535
+ if (raw === void 0 || !COUNT_PATTERN.test(raw)) return void 0;
2536
+ const parsed = Number(raw);
2537
+ return Number.isSafeInteger(parsed) ? parsed : void 0;
2426
2538
  }
2427
- function unquote(raw) {
2428
- return is_quoted(raw) ? raw.slice(FIRST_CHARACTER_END, LAST_CHARACTER_START) : raw;
2539
+ function is_readable(raw, parsed) {
2540
+ return raw === void 0 || parsed !== void 0;
2429
2541
  }
2430
- function assignment_value(line) {
2431
- return unquote(line.slice(line.indexOf(ASSIGNMENT_SEPARATOR) + FIRST_CHARACTER_END).trim());
2542
+ var COUNT_NAMES = ["idle", "max", "merged", "running"];
2543
+ function counts_of(values) {
2544
+ return {
2545
+ idle: to_count(text_of(values.idle)),
2546
+ max: to_count(text_of(values.max)),
2547
+ merged: to_count(text_of(values.merged)),
2548
+ running: to_count(text_of(values.running))
2549
+ };
2432
2550
  }
2433
- function read_assignment(content, key) {
2434
- const line = split_lines(content).findLast((entry) => is_key_line(entry, key));
2435
- return line === void 0 ? void 0 : assignment_value(line);
2551
+ function are_counts_readable(values, counts) {
2552
+ return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
2436
2553
  }
2437
- function read_root_seed(content) {
2438
- return ports.resolve_seed({ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY) });
2554
+ var NO_IDLE_MINUTES = 0;
2555
+ function to_idle_ms(idle_minutes) {
2556
+ if (idle_minutes === void 0) return backlog_budget.DEFAULT_IDLE_MS;
2557
+ if (idle_minutes === NO_IDLE_MINUTES) return void 0;
2558
+ return idle_minutes * backlog_budget.MS_PER_MINUTE;
2439
2559
  }
2440
- function read_lane_seat(content) {
2441
- const raw = read_assignment(content, LANE_SEAT_KEY);
2442
- return raw === void 0 ? void 0 : ports.resolve_lane({ [LANE_SEAT_KEY]: raw });
2560
+ function is_watch_readable(values) {
2561
+ if (to_idle_ms(to_count(text_of(values.idle))) === void 0) return true;
2562
+ return text_of(values.active) !== void 0;
2443
2563
  }
2444
- function read_lane_ports(content) {
2445
- const environment = {
2446
- [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY),
2447
- [LANE_SEAT_KEY]: read_assignment(content, LANE_SEAT_KEY)
2448
- };
2564
+ function are_values_readable(values) {
2565
+ return is_readable(text_of(values.active), to_time_ms(text_of(values.active))) && is_watch_readable(values) && are_counts_readable(values, counts_of(values));
2566
+ }
2567
+ function input_of(values, base) {
2568
+ const counts = counts_of(values);
2449
2569
  return {
2450
- development: ports.resolve_development_port(environment),
2451
- preview: ports.resolve_preview_port(environment)
2570
+ ...base,
2571
+ active_at_ms: to_time_ms(text_of(values.active)) ?? base.started_at_ms,
2572
+ merged: counts.merged ?? 0,
2573
+ running: counts.running ?? 0,
2574
+ max_issues: counts.max,
2575
+ idle_budget_ms: to_idle_ms(counts.idle)
2452
2576
  };
2453
2577
  }
2454
- function read_lane_output(content) {
2455
- const line = split_lines(content).findLast((entry) => is_output_line(entry));
2456
- const raw = line === void 0 ? void 0 : assignment_value(line);
2457
- return raw === void 0 || raw.length === 0 ? void 0 : raw;
2578
+ function build_input(values, now_ms) {
2579
+ const answer = to_answer(text_of(values.answer));
2580
+ if (answer === void 0) return void 0;
2581
+ const started_at_ms = to_time_ms(text_of(values.started));
2582
+ if (started_at_ms === void 0) return void 0;
2583
+ if (!are_values_readable(values)) return void 0;
2584
+ return input_of(values, { answer, now_ms, started_at_ms });
2458
2585
  }
2459
- function read_lane_profile(content) {
2460
- return agent_role_profile.parse({
2461
- provider: read_assignment(content, LANE_PROVIDER_KEY),
2462
- role: read_assignment(content, LANE_ROLE_KEY),
2463
- model: read_assignment(content, LANE_MODEL_KEY),
2464
- effort: read_assignment(content, LANE_EFFORT_KEY)
2465
- });
2586
+ function refuse() {
2587
+ console.error(USAGE);
2588
+ return FAILURE_EXIT_CODE2;
2466
2589
  }
2467
- function replace_assignment(lines, keep, assignment, key) {
2468
- return lines.flatMap((entry, index) => {
2469
- if (index === keep) return [assignment];
2470
- return is_key_line(entry, key) ? [] : [entry];
2471
- });
2590
+ function run(argv, now_ms = Date.now()) {
2591
+ const values = read_arguments(argv);
2592
+ if (values === void 0) return refuse();
2593
+ const input = build_input(values, now_ms);
2594
+ if (input === void 0) return refuse();
2595
+ const { verdict, reason } = backlog_budget.decide(input);
2596
+ if (values.json === true) {
2597
+ console.info(
2598
+ JSON.stringify({ [JSON_KEY]: verdict, reason, is_finish: backlog_budget.is_finish(input) })
2599
+ );
2600
+ return SUCCESS_EXIT_CODE;
2601
+ }
2602
+ path_decision.print_decision(JSON_KEY, verdict, reason, false);
2603
+ return SUCCESS_EXIT_CODE;
2472
2604
  }
2473
- function upsert_assignment(content, key, value) {
2474
- const assignment = `${key}${ASSIGNMENT_SEPARATOR}${value}`;
2475
- const lines = split_lines(content);
2476
- const last = lines.findLastIndex((entry) => is_key_line(entry, key));
2477
- const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key);
2478
- return `${body.join("\n")}
2479
- `;
2480
- }
2481
- function lane_file_content(root_content, seat) {
2482
- return upsert_assignment(root_content, LANE_SEAT_KEY, String(seat));
2483
- }
2484
- function with_lane_output(content, output, profile) {
2485
- const recorded = upsert_assignment(content, LANE_OUTPUT_KEY, `"${output}"`);
2486
- if (profile === void 0) return recorded;
2487
- const with_provider = upsert_assignment(recorded, LANE_PROVIDER_KEY, profile.provider);
2488
- const with_role = upsert_assignment(with_provider, LANE_ROLE_KEY, profile.role);
2489
- const with_model = upsert_assignment(with_role, LANE_MODEL_KEY, profile.model);
2490
- return upsert_assignment(with_model, LANE_EFFORT_KEY, profile.effort);
2605
+ function main(argv) {
2606
+ process.exitCode = run(argv);
2491
2607
  }
2492
- var lane_environment = {
2493
- LANE_OUTPUT_KEY,
2494
- LANE_PROVIDER_KEY,
2495
- LANE_ROLE_KEY,
2496
- is_output_line,
2497
- is_seat_line,
2498
- is_seed_line,
2499
- lane_file_content,
2500
- read_lane_output,
2501
- read_lane_profile,
2502
- read_lane_ports,
2503
- read_lane_seat,
2504
- read_root_seed,
2505
- with_lane_output
2608
+ var backlog_budget_cli = {
2609
+ JSON_KEY,
2610
+ OPTIONS,
2611
+ SUCCESS_EXIT_CODE,
2612
+ USAGE,
2613
+ build_input,
2614
+ main,
2615
+ read_arguments,
2616
+ run,
2617
+ to_answer,
2618
+ to_count,
2619
+ to_time_ms
2506
2620
  };
2621
+ if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2507
2622
 
2508
- // scripts/lane/lane-registry.ts
2509
- var WORKTREE_PREFIX = "worktree ";
2510
- var BRANCH_PREFIX = "branch refs/heads/";
2511
- var BLOCK_SEPARATOR = "\n\n";
2512
- var ISSUE_PATTERN = /^[1-9]\d*$/u;
2513
- function line_value(lines, prefix) {
2514
- return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length);
2623
+ // scripts/run/run-invocation.ts
2624
+ var BACKLOG_COMMAND = "backlogrun";
2625
+ var KNOWN_FLAGS2 = ["--max", "--idle"];
2626
+ var ONLY_FLAG = "--only";
2627
+ var ISSUE_PREFIX = "#";
2628
+ var TOKEN_SEPARATOR = " ";
2629
+ var FIRST_TOKEN = 0;
2630
+ var FIRST_ARGUMENT_INDEX = 1;
2631
+ var VALUE_OFFSET = 1;
2632
+ var FLAG_PAIR_LENGTH = 2;
2633
+ var ONLY_STEP = 1;
2634
+ var EMPTY_LENGTH2 = 0;
2635
+ var NOT_FOUND2 = -1;
2636
+ function tokens_of(invocation) {
2637
+ return invocation.split(TOKEN_SEPARATOR).filter((token) => token.length > EMPTY_LENGTH2);
2515
2638
  }
2516
- function seat_from_content(content) {
2517
- const seat = lane_environment.read_lane_seat(content);
2518
- if (seat === void 0) return void 0;
2519
- const port_pair = lane_environment.read_lane_ports(content);
2520
- return { seat, development_port: port_pair.development, preview_port: port_pair.preview };
2639
+ function flag_token(token, raw) {
2640
+ const flag = KNOWN_FLAGS2.find((known) => known === token);
2641
+ const value = backlog_budget_cli.to_count(raw);
2642
+ if (flag === void 0 || value === void 0) return void 0;
2643
+ return `${flag} ${String(value)}`;
2521
2644
  }
2522
- var EMPTY_SEAT = {
2523
- seat: void 0,
2524
- development_port: void 0,
2525
- preview_port: void 0
2526
- };
2527
- function held_seat(content) {
2528
- if (content === void 0) return EMPTY_SEAT;
2529
- return seat_from_content(content) ?? EMPTY_SEAT;
2645
+ function read_flag(tokens, index) {
2646
+ const token = tokens[index] ?? "";
2647
+ if (token === ONLY_FLAG) return { part: ONLY_FLAG, advance: ONLY_STEP };
2648
+ const part = flag_token(token, tokens[index + VALUE_OFFSET]);
2649
+ return part === void 0 ? void 0 : { part, advance: FLAG_PAIR_LENGTH };
2530
2650
  }
2531
- function read_environment(directory) {
2532
- return file_reader.read_if_readable(path3.join(directory, ENV_FILE_NAME));
2651
+ function flag_tokens(tokens, start) {
2652
+ const rendered = [];
2653
+ let index = start;
2654
+ while (index < tokens.length) {
2655
+ const read2 = read_flag(tokens, index);
2656
+ if (read2 === void 0) return void 0;
2657
+ rendered.push(read2.part);
2658
+ index += read2.advance;
2659
+ }
2660
+ return rendered;
2533
2661
  }
2534
- function branch_issue(branch2) {
2535
- if (!branch2?.endsWith(lane_paths.LANE_BRANCH_SUFFIX)) return void 0;
2536
- const issue = branch2.slice(0, -lane_paths.LANE_BRANCH_SUFFIX.length);
2537
- return ISSUE_PATTERN.test(issue) ? issue : void 0;
2662
+ function issue_of(token) {
2663
+ if (!token.startsWith(ISSUE_PREFIX)) return void 0;
2664
+ const digits = token.slice(ISSUE_PREFIX.length);
2665
+ if (!run_issue_number.ISSUE_NUMBER_PATTERN.test(digits)) return void 0;
2666
+ const issue = Number(digits);
2667
+ return Number.isSafeInteger(issue) ? issue : void 0;
2538
2668
  }
2539
- function is_gone(directory) {
2540
- return !existsSync2(directory);
2669
+ function take_leading_prefixed(args) {
2670
+ const stop = args.findIndex((token) => !token.startsWith(ISSUE_PREFIX));
2671
+ return args.slice(FIRST_TOKEN, stop === NOT_FOUND2 ? args.length : stop);
2541
2672
  }
2542
- function build_lane(issue, branch2, directory, is_stranded) {
2543
- const content = is_stranded ? void 0 : read_environment(directory);
2544
- const held = held_seat(content);
2545
- const output = content === void 0 ? void 0 : lane_environment.read_lane_output(content);
2546
- const profile = content === void 0 ? void 0 : lane_environment.read_lane_profile(content);
2547
- return {
2548
- issue,
2549
- branch: branch2,
2550
- directory,
2551
- seat: held.seat,
2552
- development_port: held.development_port,
2553
- preview_port: held.preview_port,
2554
- output,
2555
- profile,
2556
- is_stranded
2557
- };
2673
+ function leading_issues(tokens) {
2674
+ const issues = [];
2675
+ const prefixed = take_leading_prefixed(tokens.slice(FIRST_ARGUMENT_INDEX));
2676
+ for (const token of prefixed) {
2677
+ const issue = issue_of(token);
2678
+ if (issue === void 0) return void 0;
2679
+ issues.push(issue);
2680
+ }
2681
+ return issues;
2558
2682
  }
2559
- function is_lane_directory(root, issue, directory) {
2560
- return directory === lane_paths.lane_directory(root, issue);
2683
+ function issue_token(issue) {
2684
+ return `${ISSUE_PREFIX}${String(issue)}`;
2561
2685
  }
2562
- function parse_block(block, root) {
2563
- const lines = block.split("\n");
2564
- const directory = line_value(lines, WORKTREE_PREFIX);
2565
- const branch2 = line_value(lines, BRANCH_PREFIX);
2566
- const issue = branch_issue(branch2);
2567
- if (directory === void 0 || branch2 === void 0 || issue === void 0) return void 0;
2568
- if (!is_lane_directory(root, issue, directory)) return void 0;
2569
- return build_lane(issue, branch2, directory, is_gone(directory));
2686
+ function joined(command, rendered) {
2687
+ return [command, ...rendered].join(TOKEN_SEPARATOR);
2570
2688
  }
2571
- function is_lane(lane) {
2572
- return lane !== void 0;
2689
+ function rebuilt_backlog(tokens) {
2690
+ const issues = leading_issues(tokens);
2691
+ if (issues === void 0) return void 0;
2692
+ const flags = flag_tokens(tokens, FIRST_ARGUMENT_INDEX + issues.length);
2693
+ if (flags === void 0) return void 0;
2694
+ return joined(BACKLOG_COMMAND, [...issues.map((issue) => issue_token(issue)), ...flags]);
2573
2695
  }
2574
- async function worktree_blocks() {
2575
- const listing = await git_worktree.worktree_list();
2576
- return listing.split(BLOCK_SEPARATOR);
2696
+ function rebuild(invocation) {
2697
+ const tokens = tokens_of(invocation);
2698
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2699
+ return rebuilt_backlog(tokens);
2577
2700
  }
2578
- async function main_repository_root() {
2579
- return repo_discovery.main_worktree(await git_command.repository_root());
2701
+ function issue_numbers(invocation) {
2702
+ const tokens = tokens_of(invocation);
2703
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2704
+ const issues = leading_issues(tokens);
2705
+ if (issues === void 0 || issues.length === EMPTY_LENGTH2) return void 0;
2706
+ return issues;
2580
2707
  }
2581
- async function list_lanes() {
2582
- const root = lane_paths.lane_root(await main_repository_root());
2583
- const blocks = await worktree_blocks();
2584
- const lanes = blocks.map((block) => parse_block(block, root)).filter(is_lane);
2585
- return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue));
2708
+ function has_only(invocation) {
2709
+ const tokens = tokens_of(invocation);
2710
+ if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return false;
2711
+ return tokens.slice(FIRST_ARGUMENT_INDEX).includes(ONLY_FLAG);
2586
2712
  }
2587
- function used_seats(lanes) {
2588
- return lanes.map((lane) => lane.seat).filter((seat) => seat !== void 0);
2713
+ var run_invocation = {
2714
+ has_only,
2715
+ issue_numbers,
2716
+ rebuild
2717
+ };
2718
+
2719
+ // scripts/run/run-carry.ts
2720
+ var CARRY_PREFIX = "josh-run-carry-";
2721
+ var REPOSITORY_DIRECTORY_INDEX = 1;
2722
+ var CARRY_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
2723
+ var CARRY_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
2724
+ var NO_INCREMENT3 = 0;
2725
+ var MAX_CUTS = 6;
2726
+ var END_COMMAND2 = "pnpm josh run:carry --end";
2727
+ var RESUME_COMMAND = "pnpm josh run:carry --resume";
2728
+ var READ_COMMAND2 = "pnpm josh run:carry --json";
2729
+ var OWNER_ARGUMENT = '--owner "$PPID"';
2730
+ var NONE_READ2 = { kind: "none" };
2731
+ var UNREADABLE_READ3 = { kind: "unreadable" };
2732
+ var NO_OWNER = {};
2733
+ var run_carry_schema = z5.object({
2734
+ invocation: z5.string(),
2735
+ started_at: z5.string(),
2736
+ merged: z5.number(),
2737
+ merged_issues: z5.array(z5.number()).optional(),
2738
+ filed: z5.number(),
2739
+ cuts: z5.number(),
2740
+ // Defaulted, so a record written before this field existed parses with a zero streak rather than
2741
+ // failing to read — the same backward-compatibility the optional owner fields below carry.
2742
+ failures: z5.number().default(0),
2743
+ // Defaulted for the same backward-compatibility as `failures` (joshuafolkken/kit#2240).
2744
+ outages: z5.number().default(0),
2745
+ // Optional, so a record written before the outage fold existed still parses (joshuafolkken/kit#2317).
2746
+ last_outage_at: z5.string().optional(),
2747
+ // Optional, so a record written by the previous shape still parses. Read as "no owner declared",
2748
+ // which is the not-provably-live answer rather than a live one.
2749
+ owner_pid: z5.number().optional(),
2750
+ owner_start: z5.string().optional(),
2751
+ is_handed_off: z5.boolean().optional(),
2752
+ done: z5.array(z5.number()).optional(),
2753
+ // Optional, so a record written before the retrospective existed still parses (joshuafolkken/kit#2328).
2754
+ retrospective: z5.boolean().optional()
2755
+ });
2756
+ function carry_path(git_directory) {
2757
+ return stamp_file.stamp_path(CARRY_PREFIX, git_directory);
2758
+ }
2759
+ async function repository_directory() {
2760
+ const directories = await git_command.git_directories();
2761
+ return directories[REPOSITORY_DIRECTORY_INDEX];
2762
+ }
2763
+ function parse_carry(raw) {
2764
+ try {
2765
+ const parsed = run_carry_schema.safeParse(JSON.parse(raw));
2766
+ return parsed.success ? parsed.data : void 0;
2767
+ } catch {
2768
+ return void 0;
2769
+ }
2770
+ }
2771
+ function is_expired2(carry, now) {
2772
+ const started = Date.parse(carry.started_at);
2773
+ if (Number.isNaN(started)) return true;
2774
+ return now.getTime() - started > CARRY_MAX_AGE_MS;
2775
+ }
2776
+ function classify4(raw, now) {
2777
+ if (raw === void 0) return NONE_READ2;
2778
+ const carry = parse_carry(raw);
2779
+ if (carry === void 0) return UNREADABLE_READ3;
2780
+ return { kind: is_expired2(carry, now) ? "expired" : "carried", carry };
2781
+ }
2782
+ function read_carry(target, now = /* @__PURE__ */ new Date()) {
2783
+ return classify4(stamp_file.read_stamp_text(target), now);
2784
+ }
2785
+ function owner_of(pid, read2 = process_identity.read_start) {
2786
+ return { pid, start: read2(pid) };
2787
+ }
2788
+ function fresh_carry(invocation, owner, now) {
2789
+ return {
2790
+ invocation,
2791
+ started_at: now.toISOString(),
2792
+ merged: NO_INCREMENT3,
2793
+ filed: NO_INCREMENT3,
2794
+ cuts: NO_INCREMENT3,
2795
+ failures: NO_INCREMENT3,
2796
+ outages: NO_INCREMENT3,
2797
+ owner_pid: owner.pid,
2798
+ owner_start: owner.start
2799
+ };
2800
+ }
2801
+ function begin_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2802
+ const carry = fresh_carry(invocation, owner, now);
2803
+ return stamp_file.create_stamp(target, carry) ? carry : void 0;
2589
2804
  }
2590
- function unreadable_lanes(lanes) {
2591
- return lanes.filter((lane) => !lane.is_stranded && lane.seat === void 0);
2805
+ function replace_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
2806
+ stamp_file.remove_stamp(target);
2807
+ return begin_carry(target, invocation, owner, now);
2592
2808
  }
2593
- function find_lane(lanes, issue) {
2594
- return lanes.find((lane) => lane.issue === issue);
2809
+ function adopt_carry(target, carry, owner = NO_OWNER) {
2810
+ const next = {
2811
+ ...carry,
2812
+ owner_pid: owner.pid,
2813
+ owner_start: owner.start,
2814
+ is_handed_off: false
2815
+ };
2816
+ stamp_file.remove_stamp(target);
2817
+ return stamp_file.create_stamp(target, next) ? next : void 0;
2595
2818
  }
2596
- async function find_open_lane(issue) {
2597
- return find_lane(await list_lanes(), issue);
2819
+ function is_owner_live(carry) {
2820
+ return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
2598
2821
  }
2599
- var lane_registry = {
2600
- find_lane,
2601
- find_open_lane,
2602
- list_lanes,
2603
- main_repository_root,
2604
- parse_block,
2605
- read_environment,
2606
- unreadable_lanes,
2607
- used_seats
2608
- };
2609
-
2610
- // scripts/backlog/backlog-budget-cli.ts
2611
- import { fileURLToPath } from "node:url";
2612
-
2613
- // scripts/git/changed-paths.ts
2614
- function to_paths(raw) {
2615
- return raw.split("\n").map((line) => line.trim()).filter((line) => line !== "");
2822
+ function is_owned_by(carry, owner) {
2823
+ return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
2616
2824
  }
2617
- async function read_changed_paths(is_staged) {
2618
- if (is_staged) return to_paths(await git_command.diff_cached_names());
2619
- const tracked = to_paths(await git_command.diff_main_names());
2620
- const untracked = to_paths(await git_command.untracked_names());
2621
- return [...tracked, ...untracked];
2825
+ function is_foreign_live_owner(carry, owner) {
2826
+ return is_owner_live(carry) && !is_owned_by(carry, owner);
2622
2827
  }
2623
- var changed_paths = { read_changed_paths, to_paths };
2624
-
2625
- // scripts/josh/path-decision.ts
2626
- var STAGED_FLAG = "--staged";
2627
- var JSON_FLAG = "--json";
2628
- var KNOWN_FLAGS = [STAGED_FLAG, JSON_FLAG];
2629
- var FAILURE_EXIT_CODE = 1;
2630
- var MAX_LISTED_PATHS = 5;
2631
- function has_unknown_flag(argv, known) {
2632
- return argv.some((argument) => !known.includes(argument));
2828
+ function is_handed_off_to(carry, invocation) {
2829
+ return carry.is_handed_off === true && carry.invocation === invocation;
2633
2830
  }
2634
- function parse_options(argv) {
2635
- if (has_unknown_flag(argv, KNOWN_FLAGS)) return void 0;
2636
- return { is_staged: argv.includes(STAGED_FLAG), is_json: argv.includes(JSON_FLAG) };
2831
+ function classify_claim(carry, request) {
2832
+ if (is_handed_off_to(carry, request.invocation)) return "resume";
2833
+ if (is_foreign_live_owner(carry, request.owner)) return "busy";
2834
+ if (carry.invocation !== request.invocation) return "mismatch";
2835
+ return request.is_adoption ? "resume" : "standing";
2637
2836
  }
2638
- function format_path_list(paths) {
2639
- const listed = paths.slice(0, MAX_LISTED_PATHS).join(", ");
2640
- if (paths.length <= MAX_LISTED_PATHS) return listed;
2641
- return `${listed}, +${String(paths.length - MAX_LISTED_PATHS)} more`;
2837
+ function is_count_refused(carry, owner) {
2838
+ return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
2642
2839
  }
2643
- function print_decision(key, answer, reason, is_json) {
2644
- if (is_json) {
2645
- console.info(JSON.stringify({ [key]: answer, reason }));
2646
- return;
2647
- }
2648
- console.info(answer);
2649
- console.error(reason);
2840
+ function is_at_cut_cap(carry) {
2841
+ return carry.cuts >= MAX_CUTS;
2650
2842
  }
2651
- async function run_path_decision(argv, command) {
2652
- const options = parse_options(argv);
2653
- if (options === void 0) {
2654
- console.error(command.usage);
2655
- return FAILURE_EXIT_CODE;
2656
- }
2657
- const paths = await changed_paths.read_changed_paths(options.is_staged);
2658
- const answer = command.decide(paths);
2659
- print_decision(command.key, answer, command.explain(paths, answer), options.is_json);
2660
- return 0;
2843
+ function end_carry(target) {
2844
+ stamp_file.remove_stamp(target);
2661
2845
  }
2662
- var path_decision = {
2663
- format_path_list,
2664
- has_unknown_flag,
2665
- JSON_FLAG,
2666
- KNOWN_FLAGS,
2667
- MAX_LISTED_PATHS,
2668
- parse_options,
2669
- print_decision,
2670
- run_path_decision,
2671
- STAGED_FLAG
2672
- };
2673
-
2674
- // scripts/lib/cli-flags.ts
2675
- import { parseArgs } from "node:util";
2676
- function refuse_unknown_flags(argv, known_flags, command) {
2677
- const unknown = argv.filter((argument) => !known_flags.includes(argument));
2678
- if (unknown.length === 0) return void 0;
2679
- const usage = `Usage: josh ${command} [${known_flags.join("] [")}]`;
2680
- return `Unknown argument(s): ${unknown.join(" ")}
2681
- ${usage}`;
2846
+ function retrospective_done_of(read2) {
2847
+ if (read2.kind !== "carried" && read2.kind !== "expired") return false;
2848
+ return read2.carry.retrospective === true;
2682
2849
  }
2683
- function parse_or_undefined(config) {
2684
- try {
2685
- return parseArgs(config);
2686
- } catch {
2687
- return void 0;
2688
- }
2850
+ function remaining_of(carry) {
2851
+ if (carry === void 0) return void 0;
2852
+ const declared = run_invocation.issue_numbers(carry.invocation);
2853
+ if (declared === void 0) return void 0;
2854
+ const done = carry.done ?? [];
2855
+ return declared.filter((issue) => !done.includes(issue));
2689
2856
  }
2690
- var cli_flags = { refuse_unknown_flags, parse_or_undefined };
2691
-
2692
- // scripts/backlog/backlog-budget-cli.ts
2693
- var ARGV_OFFSET = 2;
2694
- var SUCCESS_EXIT_CODE = 0;
2695
- var FAILURE_EXIT_CODE2 = 1;
2696
- var JSON_KEY = "budget";
2697
- var USAGE = `Usage: josh backlog:budget --answer <${backlog_budget.ANSWERS.join("|")}> --started <ISO-8601> [--active <ISO-8601>, required unless the watch is off] [--merged <count>] [--running <count>] [--max <count>] [--idle <minutes>, 0 to turn the watch off] [--json]`;
2698
- var OPTIONS = {
2699
- active: { type: "string" },
2700
- answer: { type: "string" },
2701
- idle: { type: "string" },
2702
- json: { type: "boolean" },
2703
- max: { type: "string" },
2704
- merged: { type: "string" },
2705
- running: { type: "string" },
2706
- started: { type: "string" }
2707
- };
2708
- function read_arguments(argv) {
2709
- return cli_flags.parse_or_undefined({ args: [...argv], options: OPTIONS, strict: true })?.values;
2857
+ function done_note(carry) {
2858
+ const done = carry.done ?? [];
2859
+ return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
2710
2860
  }
2711
- function text_of(value) {
2712
- return typeof value === "string" ? value : void 0;
2861
+ function describe_carry(carry) {
2862
+ return `${carry.invocation} started ${carry.started_at}; ${String(carry.merged)} merged, ${String(carry.filed)} filed, ${String(carry.failures)} failed in a row, ${String(carry.outages)} outage(s) in a row, ${String(carry.cuts)} cut(s) crossed${done_note(carry)}`;
2713
2863
  }
2714
- function to_answer(raw) {
2715
- return backlog_budget.ANSWERS.find((answer) => answer === raw);
2864
+ function expired_message(carry) {
2865
+ return `The ${String(CARRY_MAX_AGE_HOURS)}-hour whole-run bound is spent: ${describe_carry(carry)}. Clear it with \`${END_COMMAND2}\`.`;
2716
2866
  }
2717
- function to_time_ms(raw) {
2718
- if (raw === void 0) return void 0;
2719
- const parsed = Date.parse(raw);
2720
- return Number.isNaN(parsed) ? void 0 : parsed;
2867
+ function mismatch_message(carry, invocation) {
2868
+ return `A different invocation is carried here \u2014 ${describe_carry(carry)}. This one is \`${invocation}\`. End the standing record with \`${END_COMMAND2}\` once you know its run is over, then begin again.`;
2721
2869
  }
2722
- var COUNT_PATTERN = /^\d+$/u;
2723
- function to_count(raw) {
2724
- if (raw === void 0 || !COUNT_PATTERN.test(raw)) return void 0;
2725
- const parsed = Number(raw);
2726
- return Number.isSafeInteger(parsed) ? parsed : void 0;
2870
+ function busy_message2(carry) {
2871
+ return `A live run holds this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was established. Wait for that run, or end its record with \`${END_COMMAND2}\` once you know it is over.`;
2727
2872
  }
2728
- function is_readable(raw, parsed) {
2729
- return raw === void 0 || parsed !== void 0;
2873
+ function standing_message(carry) {
2874
+ return `A run record is standing here that no cut handed off \u2014 ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${RESUME_COMMAND} "${carry.invocation}" ${OWNER_ARGUMENT}\`, or discard it with \`${END_COMMAND2}\` and begin again.`;
2730
2875
  }
2731
- var COUNT_NAMES = ["idle", "max", "merged", "running"];
2732
- function counts_of(values) {
2733
- return {
2734
- idle: to_count(text_of(values.idle)),
2735
- max: to_count(text_of(values.max)),
2736
- merged: to_count(text_of(values.merged)),
2737
- running: to_count(text_of(values.running))
2738
- };
2876
+ function count_refused_message(carry) {
2877
+ if (carry.is_handed_off === true) {
2878
+ return `This budget was handed off at a cut and is waiting for its successor \u2014 ${describe_carry(carry)}. Nothing was counted; the cutting session must not advance a handed-off budget.`;
2879
+ }
2880
+ return `A live run owns this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was counted; only its owner advances the budget.`;
2739
2881
  }
2740
- function are_counts_readable(values, counts) {
2741
- return COUNT_NAMES.every((name) => is_readable(text_of(values[name]), counts[name]));
2882
+ function lost_message() {
2883
+ return `Another process established a run record here first, so nothing was established. Read it with \`${READ_COMMAND2}\`.`;
2742
2884
  }
2743
- var NO_IDLE_MINUTES = 0;
2744
- function to_idle_ms(idle_minutes) {
2745
- if (idle_minutes === void 0) return backlog_budget.DEFAULT_IDLE_MS;
2746
- if (idle_minutes === NO_IDLE_MINUTES) return void 0;
2747
- return idle_minutes * backlog_budget.MS_PER_MINUTE;
2885
+ function nothing_to_resume_message() {
2886
+ return `Nothing is carried here, so there is no budget to resume. Begin one with \`pnpm josh run:carry --begin "<invocation>"\`.`;
2748
2887
  }
2749
- function is_watch_readable(values) {
2750
- if (to_idle_ms(to_count(text_of(values.idle))) === void 0) return true;
2751
- return text_of(values.active) !== void 0;
2888
+ function unreadable_message3() {
2889
+ return `A run record is here but could not be read; clear it with \`${END_COMMAND2}\` once you know no run is using it.`;
2752
2890
  }
2753
- function are_values_readable(values) {
2754
- return is_readable(text_of(values.active), to_time_ms(text_of(values.active))) && is_watch_readable(values) && are_counts_readable(values, counts_of(values));
2891
+ function unknown_message3() {
2892
+ return "This repository\u2019s git directory could not be read, so no run record was established.";
2755
2893
  }
2756
- function input_of(values, base) {
2757
- const counts = counts_of(values);
2758
- return {
2759
- ...base,
2760
- active_at_ms: to_time_ms(text_of(values.active)) ?? base.started_at_ms,
2761
- merged: counts.merged ?? 0,
2762
- running: counts.running ?? 0,
2763
- max_issues: counts.max,
2764
- idle_budget_ms: to_idle_ms(counts.idle)
2765
- };
2894
+ var run_carry = {
2895
+ CARRY_MAX_AGE_HOURS,
2896
+ CARRY_MAX_AGE_MS,
2897
+ END_COMMAND: END_COMMAND2,
2898
+ MAX_CUTS,
2899
+ NO_OWNER,
2900
+ RESUME_COMMAND,
2901
+ adopt_carry,
2902
+ apply_change: run_carry_change.apply_change,
2903
+ begin_carry,
2904
+ busy_message: busy_message2,
2905
+ carry_path,
2906
+ classify: classify4,
2907
+ classify_claim,
2908
+ count_refused_message,
2909
+ is_at_cut_cap,
2910
+ describe_carry,
2911
+ end_carry,
2912
+ expired_message,
2913
+ mismatch_message,
2914
+ fresh_carry,
2915
+ is_count_refused,
2916
+ is_expired: is_expired2,
2917
+ is_foreign_live_owner,
2918
+ is_owner_live,
2919
+ lost_message,
2920
+ nothing_to_resume_message,
2921
+ owner_of,
2922
+ parse_carry,
2923
+ read_carry,
2924
+ remaining_of,
2925
+ replace_carry,
2926
+ repository_directory,
2927
+ retrospective_done_of,
2928
+ standing_message,
2929
+ unknown_message: unknown_message3,
2930
+ unreadable_message: unreadable_message3
2931
+ };
2932
+
2933
+ // scripts/lane/lane-registry.ts
2934
+ import { existsSync as existsSync2 } from "node:fs";
2935
+ import path3 from "node:path";
2936
+
2937
+ // scripts/git/git-ls-remote.ts
2938
+ var LS_REMOTE_COMMAND = "ls-remote";
2939
+ var HEADS_FLAG = "--heads";
2940
+ var ORIGIN_REMOTE = "origin";
2941
+ var HEADS_REF_PREFIX = "refs/heads/";
2942
+ function ls_remote_branch_arguments(branch_name) {
2943
+ return [LS_REMOTE_COMMAND, HEADS_FLAG, ORIGIN_REMOTE, `${HEADS_REF_PREFIX}${branch_name}`];
2766
2944
  }
2767
- function build_input(values, now_ms) {
2768
- const answer = to_answer(text_of(values.answer));
2769
- if (answer === void 0) return void 0;
2770
- const started_at_ms = to_time_ms(text_of(values.started));
2771
- if (started_at_ms === void 0) return void 0;
2772
- if (!are_values_readable(values)) return void 0;
2773
- return input_of(values, { answer, now_ms, started_at_ms });
2945
+
2946
+ // scripts/git/repository-lock.ts
2947
+ import { randomUUID as randomUUID2 } from "node:crypto";
2948
+ import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
2949
+ import { setTimeout as sleep } from "node:timers/promises";
2950
+
2951
+ // scripts/git/git-common-directory.ts
2952
+ import { execFileSync as execFileSync4 } from "node:child_process";
2953
+ var OWN_GIT_DIRECTORY = 0;
2954
+ var COMMON_GIT_DIRECTORY = 1;
2955
+ var PROBE_TIMEOUT_MS3 = 2e3;
2956
+ function read_directories(cwd) {
2957
+ const git = git_utilities.get_git_command_for_spawn();
2958
+ const output = execFileSync4(git, [...git_command.GIT_DIRECTORY_ARGUMENTS], {
2959
+ cwd,
2960
+ encoding: "utf8",
2961
+ stdio: ["ignore", "pipe", "ignore"],
2962
+ timeout: PROBE_TIMEOUT_MS3
2963
+ });
2964
+ return output.trim().split("\n");
2774
2965
  }
2775
- function refuse() {
2776
- console.error(USAGE);
2777
- return FAILURE_EXIT_CODE2;
2966
+ function select_linked(directories) {
2967
+ const own = directories[OWN_GIT_DIRECTORY];
2968
+ const common = directories[COMMON_GIT_DIRECTORY];
2969
+ return own !== void 0 && common !== void 0 && own !== common ? common : void 0;
2778
2970
  }
2779
- function run(argv, now_ms = Date.now()) {
2780
- const values = read_arguments(argv);
2781
- if (values === void 0) return refuse();
2782
- const input = build_input(values, now_ms);
2783
- if (input === void 0) return refuse();
2784
- const { verdict, reason } = backlog_budget.decide(input);
2785
- if (values.json === true) {
2786
- console.info(
2787
- JSON.stringify({ [JSON_KEY]: verdict, reason, is_finish: backlog_budget.is_finish(input) })
2788
- );
2789
- return SUCCESS_EXIT_CODE;
2971
+ function resolve2(cwd) {
2972
+ try {
2973
+ return select_linked(read_directories(cwd));
2974
+ } catch {
2975
+ return void 0;
2790
2976
  }
2791
- path_decision.print_decision(JSON_KEY, verdict, reason, false);
2792
- return SUCCESS_EXIT_CODE;
2793
2977
  }
2794
- function main(argv) {
2795
- process.exitCode = run(argv);
2978
+ function repository(cwd) {
2979
+ try {
2980
+ return read_directories(cwd)[COMMON_GIT_DIRECTORY];
2981
+ } catch {
2982
+ return void 0;
2983
+ }
2796
2984
  }
2797
- var backlog_budget_cli = {
2798
- JSON_KEY,
2799
- OPTIONS,
2800
- SUCCESS_EXIT_CODE,
2801
- USAGE,
2802
- build_input,
2803
- main,
2804
- read_arguments,
2805
- run,
2806
- to_answer,
2807
- to_count,
2808
- to_time_ms
2809
- };
2810
- if (process.argv[1] === fileURLToPath(import.meta.url)) main(process.argv.slice(ARGV_OFFSET));
2985
+ var git_common_directory = { repository, resolve: resolve2, select_linked };
2811
2986
 
2812
- // scripts/run/run-invocation.ts
2813
- var BACKLOG_COMMAND = "backlogrun";
2814
- var KNOWN_FLAGS2 = ["--max", "--idle"];
2815
- var ONLY_FLAG = "--only";
2816
- var ISSUE_PREFIX = "#";
2817
- var TOKEN_SEPARATOR = " ";
2818
- var FIRST_TOKEN = 0;
2819
- var FIRST_ARGUMENT_INDEX = 1;
2820
- var VALUE_OFFSET = 1;
2821
- var FLAG_PAIR_LENGTH = 2;
2822
- var ONLY_STEP = 1;
2823
- var EMPTY_LENGTH2 = 0;
2824
- var NOT_FOUND2 = -1;
2825
- function tokens_of(invocation) {
2826
- return invocation.split(TOKEN_SEPARATOR).filter((token) => token.length > EMPTY_LENGTH2);
2827
- }
2828
- function flag_token(token, raw) {
2829
- const flag = KNOWN_FLAGS2.find((known) => known === token);
2830
- const value = backlog_budget_cli.to_count(raw);
2831
- if (flag === void 0 || value === void 0) return void 0;
2832
- return `${flag} ${String(value)}`;
2833
- }
2834
- function read_flag(tokens, index) {
2835
- const token = tokens[index] ?? "";
2836
- if (token === ONLY_FLAG) return { part: ONLY_FLAG, advance: ONLY_STEP };
2837
- const part = flag_token(token, tokens[index + VALUE_OFFSET]);
2838
- return part === void 0 ? void 0 : { part, advance: FLAG_PAIR_LENGTH };
2987
+ // scripts/git/repository-lock.ts
2988
+ var POLL_INTERVAL_MS = 200;
2989
+ function lock_path(prefix, cwd = process.cwd()) {
2990
+ return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd);
2839
2991
  }
2840
- function flag_tokens(tokens, start) {
2841
- const rendered = [];
2842
- let index = start;
2843
- while (index < tokens.length) {
2844
- const read2 = read_flag(tokens, index);
2845
- if (read2 === void 0) return void 0;
2846
- rendered.push(read2.part);
2847
- index += read2.advance;
2992
+ function parse_owner(raw) {
2993
+ if (raw === void 0) return void 0;
2994
+ try {
2995
+ return JSON.parse(raw);
2996
+ } catch {
2997
+ return void 0;
2848
2998
  }
2849
- return rendered;
2850
2999
  }
2851
- function issue_of(token) {
2852
- if (!token.startsWith(ISSUE_PREFIX)) return void 0;
2853
- const digits = token.slice(ISSUE_PREFIX.length);
2854
- if (!run_issue_number.ISSUE_NUMBER_PATTERN.test(digits)) return void 0;
2855
- const issue = Number(digits);
2856
- return Number.isSafeInteger(issue) ? issue : void 0;
3000
+ function read_owner(target) {
3001
+ return parse_owner(stamp_file.read_stamp_text(target));
2857
3002
  }
2858
- function take_leading_prefixed(args) {
2859
- const stop = args.findIndex((token) => !token.startsWith(ISSUE_PREFIX));
2860
- return args.slice(FIRST_TOKEN, stop === NOT_FOUND2 ? args.length : stop);
3003
+ function is_owner_gone(owner) {
3004
+ return process_identity.is_same_process(owner.pid, owner.process_start) === false;
2861
3005
  }
2862
- function leading_issues(tokens) {
2863
- const issues = [];
2864
- const prefixed = take_leading_prefixed(tokens.slice(FIRST_ARGUMENT_INDEX));
2865
- for (const token of prefixed) {
2866
- const issue = issue_of(token);
2867
- if (issue === void 0) return void 0;
2868
- issues.push(issue);
3006
+ function restore(aside, target) {
3007
+ try {
3008
+ linkSync(aside, target);
3009
+ } catch {
2869
3010
  }
2870
- return issues;
2871
3011
  }
2872
- function issue_token(issue) {
2873
- return `${ISSUE_PREFIX}${String(issue)}`;
3012
+ function stale_record(target, is_gone2) {
3013
+ const raw = stamp_file.read_stamp_text(target);
3014
+ const owner = parse_owner(raw);
3015
+ return owner !== void 0 && is_gone2(owner) ? raw : void 0;
2874
3016
  }
2875
- function joined(command, rendered) {
2876
- return [command, ...rendered].join(TOKEN_SEPARATOR);
3017
+ function move_aside(target) {
3018
+ const aside = `${target}.${String(process.pid)}.${randomUUID2()}`;
3019
+ try {
3020
+ renameSync(target, aside);
3021
+ return aside;
3022
+ } catch {
3023
+ return void 0;
3024
+ }
2877
3025
  }
2878
- function rebuilt_backlog(tokens) {
2879
- const issues = leading_issues(tokens);
2880
- if (issues === void 0) return void 0;
2881
- const flags = flag_tokens(tokens, FIRST_ARGUMENT_INDEX + issues.length);
2882
- if (flags === void 0) return void 0;
2883
- return joined(BACKLOG_COMMAND, [...issues.map((issue) => issue_token(issue)), ...flags]);
3026
+ function clear_stale(target, is_gone2 = is_owner_gone) {
3027
+ const raw = stale_record(target, is_gone2);
3028
+ if (raw === void 0) return;
3029
+ const aside = move_aside(target);
3030
+ if (aside === void 0) return;
3031
+ if (stamp_file.read_stamp_text(aside) !== raw) restore(aside, target);
3032
+ rmSync2(aside, { force: true });
2884
3033
  }
2885
- function rebuild(invocation) {
2886
- const tokens = tokens_of(invocation);
2887
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2888
- return rebuilt_backlog(tokens);
3034
+ function claim(target) {
3035
+ clear_stale(target);
3036
+ return stamp_file.create_stamp(target, process_identity.own_fields());
2889
3037
  }
2890
- function issue_numbers(invocation) {
2891
- const tokens = tokens_of(invocation);
2892
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return void 0;
2893
- const issues = leading_issues(tokens);
2894
- if (issues === void 0 || issues.length === EMPTY_LENGTH2) return void 0;
2895
- return issues;
3038
+ function release(target) {
3039
+ const owner = read_owner(target);
3040
+ if (owner !== void 0 && process_identity.is_own_process(owner.pid, owner.process_start)) {
3041
+ stamp_file.remove_stamp(target);
3042
+ }
2896
3043
  }
2897
- function has_only(invocation) {
2898
- const tokens = tokens_of(invocation);
2899
- if (tokens[FIRST_TOKEN] !== BACKLOG_COMMAND) return false;
2900
- return tokens.slice(FIRST_ARGUMENT_INDEX).includes(ONLY_FLAG);
3044
+ async function acquire(target, max_wait_ms) {
3045
+ const deadline = Date.now() + max_wait_ms;
3046
+ while (!claim(target)) {
3047
+ if (Date.now() >= deadline) return false;
3048
+ await sleep(POLL_INTERVAL_MS);
3049
+ }
3050
+ return true;
2901
3051
  }
2902
- var run_invocation = {
2903
- has_only,
2904
- issue_numbers,
2905
- rebuild
2906
- };
2907
-
2908
- // scripts/run/run-carry.ts
2909
- import { z as z5 } from "zod";
3052
+ async function with_lock(work, target, max_wait_ms) {
3053
+ if (!await acquire(target, max_wait_ms)) return void 0;
3054
+ try {
3055
+ return await work();
3056
+ } finally {
3057
+ release(target);
3058
+ }
3059
+ }
3060
+ var repository_lock = { clear_stale, lock_path, with_lock };
2910
3061
 
2911
- // scripts/run/run-carry-streak.ts
2912
- var NO_INCREMENT = 0;
2913
- var OUTAGE_FOLD_WINDOW_MS = 12e4;
2914
- function has_increment(value) {
2915
- return (value ?? NO_INCREMENT) > NO_INCREMENT;
3062
+ // scripts/git/git-worktree.ts
3063
+ var WORKTREE = "worktree";
3064
+ var WORKTREE_LOCK_PREFIX = "josh-worktree-lock-";
3065
+ var WORKTREE_LOCK_MAX_WAIT_MS = 12e4;
3066
+ async function locked(arguments_) {
3067
+ const target = repository_lock.lock_path(WORKTREE_LOCK_PREFIX);
3068
+ const output = await repository_lock.with_lock(
3069
+ async () => await git_spawn.read(arguments_),
3070
+ target,
3071
+ WORKTREE_LOCK_MAX_WAIT_MS
3072
+ );
3073
+ if (output === void 0) {
3074
+ throw new Error(
3075
+ `git ${arguments_.join(" ")}: timed out waiting for the work-tree lock ${target}`
3076
+ );
3077
+ }
3078
+ return output;
3079
+ }
3080
+ async function worktree_list() {
3081
+ return await git_spawn.read([WORKTREE, "list", PORCELAIN_FLAG]);
2916
3082
  }
2917
- function next_failures(carry, change) {
2918
- if (has_increment(change.merged)) return NO_INCREMENT;
2919
- return carry.failures + (change.failures ?? NO_INCREMENT);
3083
+ async function worktree_add(directory, branch_name, start_point) {
3084
+ const flags = start_point === void 0 ? ["add"] : ["add", "--no-track", "-b", branch_name];
3085
+ return await locked([WORKTREE, ...flags, directory, start_point ?? branch_name]);
2920
3086
  }
2921
- function is_within_fold_window(last_outage_at, now) {
2922
- if (last_outage_at === void 0) return false;
2923
- const last = Date.parse(last_outage_at);
2924
- if (Number.isNaN(last)) return false;
2925
- return now.getTime() - last <= OUTAGE_FOLD_WINDOW_MS;
3087
+ async function worktree_add_detached(directory, commit2) {
3088
+ return await locked([WORKTREE, "add", "--detach", directory, commit2]);
2926
3089
  }
2927
- function folded_outage(carry) {
2928
- return { outages: carry.outages, last_outage_at: carry.last_outage_at };
3090
+ async function worktree_remove(directory) {
3091
+ return await locked([WORKTREE, "remove", "--force", directory]);
2929
3092
  }
2930
- function counted_outage(carry, change, now) {
2931
- const outages = carry.outages + (change.outages ?? NO_INCREMENT);
2932
- return { outages, last_outage_at: now.toISOString() };
3093
+ async function worktree_prune() {
3094
+ return await locked([WORKTREE, "prune"]);
2933
3095
  }
2934
- function next_outage(carry, change, now) {
2935
- if (has_increment(change.outages)) {
2936
- return is_within_fold_window(carry.last_outage_at, now) ? folded_outage(carry) : counted_outage(carry, change, now);
2937
- }
2938
- const is_reachable = has_increment(change.merged) || has_increment(change.failures);
2939
- return is_reachable ? { outages: NO_INCREMENT, last_outage_at: void 0 } : folded_outage(carry);
3096
+ async function ls_remote_branch(branch_name) {
3097
+ return await git_spawn.read(ls_remote_branch_arguments(branch_name));
2940
3098
  }
2941
- function next_state(carry, change, now) {
2942
- const outage = next_outage(carry, change, now);
2943
- return {
2944
- failures: next_failures(carry, change),
2945
- outages: outage.outages,
2946
- last_outage_at: outage.last_outage_at
2947
- };
3099
+ async function branch_delete(branch_name) {
3100
+ return await git_spawn.read(["branch", "-D", branch_name]);
2948
3101
  }
2949
- var run_carry_streak = { OUTAGE_FOLD_WINDOW_MS, next_state };
3102
+ var git_worktree = {
3103
+ branch_delete,
3104
+ ls_remote_branch,
3105
+ worktree_add,
3106
+ worktree_add_detached,
3107
+ worktree_list,
3108
+ worktree_prune,
3109
+ worktree_remove
3110
+ };
2950
3111
 
2951
- // scripts/run/run-carry-change.ts
2952
- var NO_INCREMENT2 = 0;
2953
- function next_done(carry, done) {
2954
- if (done === void 0) return carry.done;
2955
- const current = carry.done ?? [];
2956
- return current.includes(done) ? current : [...current, done];
3112
+ // scripts/lane/lane-environment.ts
3113
+ var LANE_OUTPUT_KEY = "JOSH_LANE_OUTPUT";
3114
+ var LANE_PROVIDER_KEY = "JOSH_LANE_AGENT_PROVIDER";
3115
+ var LANE_ROLE_KEY = "JOSH_LANE_AGENT_ROLE";
3116
+ var LANE_MODEL_KEY = "JOSH_LANE_AGENT_MODEL";
3117
+ var LANE_EFFORT_KEY = "JOSH_LANE_AGENT_EFFORT";
3118
+ var ASSIGNMENT_SEPARATOR = "=";
3119
+ var EXPORT_PREFIX = "export ";
3120
+ var QUOTE_CHARACTERS = /* @__PURE__ */ new Set(['"', "'"]);
3121
+ var MIN_QUOTED_LENGTH = 2;
3122
+ var FIRST_CHARACTER_END = 1;
3123
+ var LAST_CHARACTER_START = -1;
3124
+ function strip_export(line) {
3125
+ const trimmed2 = line.trimStart();
3126
+ return trimmed2.startsWith(EXPORT_PREFIX) ? trimmed2.slice(EXPORT_PREFIX.length).trimStart() : trimmed2;
2957
3127
  }
2958
- function next_merged_issues(carry, issue) {
2959
- if (issue === void 0) return carry.merged_issues;
2960
- return [...carry.merged_issues ?? [], issue];
3128
+ function is_key_line(line, key) {
3129
+ const body = strip_export(line);
3130
+ if (!body.startsWith(key)) return false;
3131
+ return body.slice(key.length).trimStart().startsWith(ASSIGNMENT_SEPARATOR);
2961
3132
  }
2962
- function is_duplicate_merge(carry, change) {
2963
- const issue = change.merged_issue;
2964
- return issue !== void 0 && carry.merged_issues?.includes(issue) === true;
3133
+ function is_seed_line(line) {
3134
+ return is_key_line(line, PORT_SEED_KEY);
2965
3135
  }
2966
- function next_counts(carry, change) {
2967
- const cuts = change.cuts ?? NO_INCREMENT2;
2968
- return {
2969
- merged: carry.merged + (change.merged ?? NO_INCREMENT2),
2970
- filed: carry.filed + (change.filed ?? NO_INCREMENT2),
2971
- cuts: carry.cuts + cuts,
2972
- is_handed_off: cuts > NO_INCREMENT2
2973
- };
3136
+ function is_seat_line(line) {
3137
+ return is_key_line(line, LANE_SEAT_KEY);
2974
3138
  }
2975
- function apply_change(target, carry, change, now = /* @__PURE__ */ new Date()) {
2976
- if (is_duplicate_merge(carry, change)) return carry;
2977
- const streak = run_carry_streak.next_state(carry, change, now);
2978
- const next = {
2979
- ...carry,
2980
- ...next_counts(carry, change),
2981
- merged_issues: next_merged_issues(carry, change.merged_issue),
2982
- failures: streak.failures,
2983
- outages: streak.outages,
2984
- last_outage_at: streak.last_outage_at,
2985
- done: next_done(carry, change.done),
2986
- retrospective: change.retrospective === true || carry.retrospective
2987
- };
2988
- stamp_file.write_stamp(target, next);
2989
- return next;
3139
+ function is_output_line(line) {
3140
+ return is_key_line(line, LANE_OUTPUT_KEY);
2990
3141
  }
2991
- var run_carry_change = { apply_change };
2992
-
2993
- // scripts/run/run-carry.ts
2994
- var CARRY_PREFIX = "josh-run-carry-";
2995
- var REPOSITORY_DIRECTORY_INDEX = 1;
2996
- var CARRY_MAX_AGE_HOURS = backlog_budget.WHOLE_RUN_BUDGET_HOURS;
2997
- var CARRY_MAX_AGE_MS = backlog_budget.WHOLE_RUN_BUDGET_MS;
2998
- var NO_INCREMENT3 = 0;
2999
- var MAX_CUTS = 6;
3000
- var END_COMMAND2 = "pnpm josh run:carry --end";
3001
- var RESUME_COMMAND = "pnpm josh run:carry --resume";
3002
- var READ_COMMAND2 = "pnpm josh run:carry --json";
3003
- var OWNER_ARGUMENT = '--owner "$PPID"';
3004
- var NONE_READ2 = { kind: "none" };
3005
- var UNREADABLE_READ3 = { kind: "unreadable" };
3006
- var NO_OWNER = {};
3007
- var run_carry_schema = z5.object({
3008
- invocation: z5.string(),
3009
- started_at: z5.string(),
3010
- merged: z5.number(),
3011
- merged_issues: z5.array(z5.number()).optional(),
3012
- filed: z5.number(),
3013
- cuts: z5.number(),
3014
- // Defaulted, so a record written before this field existed parses with a zero streak rather than
3015
- // failing to read — the same backward-compatibility the optional owner fields below carry.
3016
- failures: z5.number().default(0),
3017
- // Defaulted for the same backward-compatibility as `failures` (joshuafolkken/kit#2240).
3018
- outages: z5.number().default(0),
3019
- // Optional, so a record written before the outage fold existed still parses (joshuafolkken/kit#2317).
3020
- last_outage_at: z5.string().optional(),
3021
- // Optional, so a record written by the previous shape still parses. Read as "no owner declared",
3022
- // which is the not-provably-live answer rather than a live one.
3023
- owner_pid: z5.number().optional(),
3024
- owner_start: z5.string().optional(),
3025
- is_handed_off: z5.boolean().optional(),
3026
- done: z5.array(z5.number()).optional(),
3027
- // Optional, so a record written before the retrospective existed still parses (joshuafolkken/kit#2328).
3028
- retrospective: z5.boolean().optional()
3029
- });
3030
- function carry_path(git_directory) {
3031
- return stamp_file.stamp_path(CARRY_PREFIX, git_directory);
3142
+ function split_lines(content) {
3143
+ const trimmed2 = content.trimEnd();
3144
+ return trimmed2.length === 0 ? [] : trimmed2.split("\n");
3032
3145
  }
3033
- async function repository_directory() {
3034
- const directories = await git_command.git_directories();
3035
- return directories[REPOSITORY_DIRECTORY_INDEX];
3146
+ function is_quoted(raw) {
3147
+ const first = raw.slice(0, FIRST_CHARACTER_END);
3148
+ if (!QUOTE_CHARACTERS.has(first)) return false;
3149
+ return raw.length >= MIN_QUOTED_LENGTH && raw.endsWith(first);
3036
3150
  }
3037
- function parse_carry(raw) {
3038
- try {
3039
- const parsed = run_carry_schema.safeParse(JSON.parse(raw));
3040
- return parsed.success ? parsed.data : void 0;
3041
- } catch {
3042
- return void 0;
3043
- }
3151
+ function unquote(raw) {
3152
+ return is_quoted(raw) ? raw.slice(FIRST_CHARACTER_END, LAST_CHARACTER_START) : raw;
3044
3153
  }
3045
- function is_expired2(carry, now) {
3046
- const started = Date.parse(carry.started_at);
3047
- if (Number.isNaN(started)) return true;
3048
- return now.getTime() - started > CARRY_MAX_AGE_MS;
3154
+ function assignment_value(line) {
3155
+ return unquote(line.slice(line.indexOf(ASSIGNMENT_SEPARATOR) + FIRST_CHARACTER_END).trim());
3049
3156
  }
3050
- function classify4(raw, now) {
3051
- if (raw === void 0) return NONE_READ2;
3052
- const carry = parse_carry(raw);
3053
- if (carry === void 0) return UNREADABLE_READ3;
3054
- return { kind: is_expired2(carry, now) ? "expired" : "carried", carry };
3157
+ function read_assignment(content, key) {
3158
+ const line = split_lines(content).findLast((entry) => is_key_line(entry, key));
3159
+ return line === void 0 ? void 0 : assignment_value(line);
3055
3160
  }
3056
- function read_carry(target, now = /* @__PURE__ */ new Date()) {
3057
- return classify4(stamp_file.read_stamp_text(target), now);
3161
+ function read_root_seed(content) {
3162
+ return ports.resolve_seed({ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY) });
3058
3163
  }
3059
- function owner_of(pid, read2 = process_identity.read_start) {
3060
- return { pid, start: read2(pid) };
3164
+ function read_lane_seat(content) {
3165
+ const raw = read_assignment(content, LANE_SEAT_KEY);
3166
+ return raw === void 0 ? void 0 : ports.resolve_lane({ [LANE_SEAT_KEY]: raw });
3061
3167
  }
3062
- function fresh_carry(invocation, owner, now) {
3168
+ function read_lane_ports(content) {
3169
+ const environment2 = {
3170
+ [PORT_SEED_KEY]: read_assignment(content, PORT_SEED_KEY),
3171
+ [LANE_SEAT_KEY]: read_assignment(content, LANE_SEAT_KEY)
3172
+ };
3063
3173
  return {
3064
- invocation,
3065
- started_at: now.toISOString(),
3066
- merged: NO_INCREMENT3,
3067
- filed: NO_INCREMENT3,
3068
- cuts: NO_INCREMENT3,
3069
- failures: NO_INCREMENT3,
3070
- outages: NO_INCREMENT3,
3071
- owner_pid: owner.pid,
3072
- owner_start: owner.start
3174
+ development: ports.resolve_development_port(environment2),
3175
+ preview: ports.resolve_preview_port(environment2)
3073
3176
  };
3074
3177
  }
3075
- function begin_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
3076
- const carry = fresh_carry(invocation, owner, now);
3077
- return stamp_file.create_stamp(target, carry) ? carry : void 0;
3078
- }
3079
- function replace_carry(target, invocation, owner = NO_OWNER, now = /* @__PURE__ */ new Date()) {
3080
- stamp_file.remove_stamp(target);
3081
- return begin_carry(target, invocation, owner, now);
3082
- }
3083
- function adopt_carry(target, carry, owner = NO_OWNER) {
3084
- const next = {
3085
- ...carry,
3086
- owner_pid: owner.pid,
3087
- owner_start: owner.start,
3088
- is_handed_off: false
3089
- };
3090
- stamp_file.remove_stamp(target);
3091
- return stamp_file.create_stamp(target, next) ? next : void 0;
3178
+ function read_lane_output(content) {
3179
+ const line = split_lines(content).findLast((entry) => is_output_line(entry));
3180
+ const raw = line === void 0 ? void 0 : assignment_value(line);
3181
+ return raw === void 0 || raw.length === 0 ? void 0 : raw;
3092
3182
  }
3093
- function is_owner_live(carry) {
3094
- return process_identity.is_same_process(carry.owner_pid, carry.owner_start) !== false;
3183
+ function read_lane_profile(content) {
3184
+ return agent_role_profile.parse({
3185
+ provider: read_assignment(content, LANE_PROVIDER_KEY),
3186
+ role: read_assignment(content, LANE_ROLE_KEY),
3187
+ model: read_assignment(content, LANE_MODEL_KEY),
3188
+ effort: read_assignment(content, LANE_EFFORT_KEY)
3189
+ });
3095
3190
  }
3096
- function is_owned_by(carry, owner) {
3097
- return carry.owner_pid === owner.pid && carry.owner_start === owner.start;
3191
+ function replace_assignment(lines, keep, assignment, key) {
3192
+ return lines.flatMap((entry, index) => {
3193
+ if (index === keep) return [assignment];
3194
+ return is_key_line(entry, key) ? [] : [entry];
3195
+ });
3098
3196
  }
3099
- function is_foreign_live_owner(carry, owner) {
3100
- return is_owner_live(carry) && !is_owned_by(carry, owner);
3197
+ function upsert_assignment(content, key, value) {
3198
+ const assignment = `${key}${ASSIGNMENT_SEPARATOR}${value}`;
3199
+ const lines = split_lines(content);
3200
+ const last = lines.findLastIndex((entry) => is_key_line(entry, key));
3201
+ const body = last === -1 ? [...lines, assignment] : replace_assignment(lines, last, assignment, key);
3202
+ return `${body.join("\n")}
3203
+ `;
3101
3204
  }
3102
- function is_handed_off_to(carry, invocation) {
3103
- return carry.is_handed_off === true && carry.invocation === invocation;
3205
+ function lane_file_content(root_content, seat) {
3206
+ return upsert_assignment(root_content, LANE_SEAT_KEY, String(seat));
3104
3207
  }
3105
- function classify_claim(carry, request) {
3106
- if (is_handed_off_to(carry, request.invocation)) return "resume";
3107
- if (is_foreign_live_owner(carry, request.owner)) return "busy";
3108
- if (carry.invocation !== request.invocation) return "mismatch";
3109
- return request.is_adoption ? "resume" : "standing";
3208
+ function with_lane_output(content, output, profile) {
3209
+ const recorded = upsert_assignment(content, LANE_OUTPUT_KEY, `"${output}"`);
3210
+ if (profile === void 0) return recorded;
3211
+ const with_provider = upsert_assignment(recorded, LANE_PROVIDER_KEY, profile.provider);
3212
+ const with_role = upsert_assignment(with_provider, LANE_ROLE_KEY, profile.role);
3213
+ const with_model = upsert_assignment(with_role, LANE_MODEL_KEY, profile.model);
3214
+ return upsert_assignment(with_model, LANE_EFFORT_KEY, profile.effort);
3110
3215
  }
3111
- function is_count_refused(carry, owner) {
3112
- return carry.is_handed_off === true || is_foreign_live_owner(carry, owner);
3216
+ var lane_environment = {
3217
+ LANE_OUTPUT_KEY,
3218
+ LANE_PROVIDER_KEY,
3219
+ LANE_ROLE_KEY,
3220
+ is_output_line,
3221
+ is_seat_line,
3222
+ is_seed_line,
3223
+ lane_file_content,
3224
+ read_lane_output,
3225
+ read_lane_profile,
3226
+ read_lane_ports,
3227
+ read_lane_seat,
3228
+ read_root_seed,
3229
+ with_lane_output
3230
+ };
3231
+
3232
+ // scripts/lane/lane-registry.ts
3233
+ var WORKTREE_PREFIX = "worktree ";
3234
+ var BRANCH_PREFIX = "branch refs/heads/";
3235
+ var BLOCK_SEPARATOR = "\n\n";
3236
+ var ISSUE_PATTERN = /^[1-9]\d*$/u;
3237
+ function line_value(lines, prefix) {
3238
+ return lines.find((line) => line.startsWith(prefix))?.slice(prefix.length);
3113
3239
  }
3114
- function is_at_cut_cap(carry) {
3115
- return carry.cuts >= MAX_CUTS;
3240
+ function seat_from_content(content) {
3241
+ const seat = lane_environment.read_lane_seat(content);
3242
+ if (seat === void 0) return void 0;
3243
+ const port_pair = lane_environment.read_lane_ports(content);
3244
+ return { seat, development_port: port_pair.development, preview_port: port_pair.preview };
3116
3245
  }
3117
- function end_carry(target) {
3118
- stamp_file.remove_stamp(target);
3246
+ var EMPTY_SEAT = {
3247
+ seat: void 0,
3248
+ development_port: void 0,
3249
+ preview_port: void 0
3250
+ };
3251
+ function held_seat(content) {
3252
+ if (content === void 0) return EMPTY_SEAT;
3253
+ return seat_from_content(content) ?? EMPTY_SEAT;
3119
3254
  }
3120
- function retrospective_done_of(read2) {
3121
- if (read2.kind !== "carried" && read2.kind !== "expired") return false;
3122
- return read2.carry.retrospective === true;
3255
+ function read_environment(directory) {
3256
+ return file_reader.read_if_readable(path3.join(directory, ENV_FILE_NAME));
3123
3257
  }
3124
- function remaining_of(carry) {
3125
- if (carry === void 0) return void 0;
3126
- const declared = run_invocation.issue_numbers(carry.invocation);
3127
- if (declared === void 0) return void 0;
3128
- const done = carry.done ?? [];
3129
- return declared.filter((issue) => !done.includes(issue));
3258
+ function branch_issue(branch2) {
3259
+ if (!branch2?.endsWith(lane_paths.LANE_BRANCH_SUFFIX)) return void 0;
3260
+ const issue = branch2.slice(0, -lane_paths.LANE_BRANCH_SUFFIX.length);
3261
+ return ISSUE_PATTERN.test(issue) ? issue : void 0;
3130
3262
  }
3131
- function done_note(carry) {
3132
- const done = carry.done ?? [];
3133
- return done.length === 0 ? "" : `, issues ${done.join(", ")} done`;
3263
+ function is_gone(directory) {
3264
+ return !existsSync2(directory);
3134
3265
  }
3135
- function describe_carry(carry) {
3136
- return `${carry.invocation} started ${carry.started_at}; ${String(carry.merged)} merged, ${String(carry.filed)} filed, ${String(carry.failures)} failed in a row, ${String(carry.outages)} outage(s) in a row, ${String(carry.cuts)} cut(s) crossed${done_note(carry)}`;
3266
+ function build_lane(issue, branch2, directory, is_stranded) {
3267
+ const content = is_stranded ? void 0 : read_environment(directory);
3268
+ const held = held_seat(content);
3269
+ const output = content === void 0 ? void 0 : lane_environment.read_lane_output(content);
3270
+ const profile = content === void 0 ? void 0 : lane_environment.read_lane_profile(content);
3271
+ return {
3272
+ issue,
3273
+ branch: branch2,
3274
+ directory,
3275
+ seat: held.seat,
3276
+ development_port: held.development_port,
3277
+ preview_port: held.preview_port,
3278
+ output,
3279
+ profile,
3280
+ is_stranded
3281
+ };
3137
3282
  }
3138
- function expired_message(carry) {
3139
- return `The ${String(CARRY_MAX_AGE_HOURS)}-hour whole-run bound is spent: ${describe_carry(carry)}. Clear it with \`${END_COMMAND2}\`.`;
3283
+ function is_lane_directory(root, issue, directory) {
3284
+ return directory === lane_paths.lane_directory(root, issue);
3140
3285
  }
3141
- function mismatch_message(carry, invocation) {
3142
- return `A different invocation is carried here \u2014 ${describe_carry(carry)}. This one is \`${invocation}\`. End the standing record with \`${END_COMMAND2}\` once you know its run is over, then begin again.`;
3286
+ function parse_block(block, root) {
3287
+ const lines = block.split("\n");
3288
+ const directory = line_value(lines, WORKTREE_PREFIX);
3289
+ const branch2 = line_value(lines, BRANCH_PREFIX);
3290
+ const issue = branch_issue(branch2);
3291
+ if (directory === void 0 || branch2 === void 0 || issue === void 0) return void 0;
3292
+ if (!is_lane_directory(root, issue, directory)) return void 0;
3293
+ return build_lane(issue, branch2, directory, is_gone(directory));
3143
3294
  }
3144
- function busy_message2(carry) {
3145
- return `A live run holds this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was established. Wait for that run, or end its record with \`${END_COMMAND2}\` once you know it is over.`;
3295
+ function is_lane(lane) {
3296
+ return lane !== void 0;
3146
3297
  }
3147
- function standing_message(carry) {
3148
- return `A run record is standing here that no cut handed off \u2014 ${describe_carry(carry)}. Nothing was established. Carry that budget with \`${RESUME_COMMAND} "${carry.invocation}" ${OWNER_ARGUMENT}\`, or discard it with \`${END_COMMAND2}\` and begin again.`;
3298
+ async function worktree_blocks() {
3299
+ const listing = await git_worktree.worktree_list();
3300
+ return listing.split(BLOCK_SEPARATOR);
3149
3301
  }
3150
- function count_refused_message(carry) {
3151
- if (carry.is_handed_off === true) {
3152
- return `This budget was handed off at a cut and is waiting for its successor \u2014 ${describe_carry(carry)}. Nothing was counted; the cutting session must not advance a handed-off budget.`;
3153
- }
3154
- return `A live run owns this budget \u2014 ${describe_carry(carry)}, owner pid ${String(carry.owner_pid)} still running. Nothing was counted; only its owner advances the budget.`;
3302
+ async function main_repository_root() {
3303
+ return repo_discovery.main_worktree(await git_command.repository_root());
3155
3304
  }
3156
- function lost_message() {
3157
- return `Another process established a run record here first, so nothing was established. Read it with \`${READ_COMMAND2}\`.`;
3305
+ async function list_lanes() {
3306
+ const root = lane_paths.lane_root(await main_repository_root());
3307
+ const blocks = await worktree_blocks();
3308
+ const lanes = blocks.map((block) => parse_block(block, root)).filter(is_lane);
3309
+ return lanes.toSorted((left, right) => Number(left.issue) - Number(right.issue));
3158
3310
  }
3159
- function nothing_to_resume_message() {
3160
- return `Nothing is carried here, so there is no budget to resume. Begin one with \`pnpm josh run:carry --begin "<invocation>"\`.`;
3311
+ function used_seats(lanes) {
3312
+ return lanes.map((lane) => lane.seat).filter((seat) => seat !== void 0);
3161
3313
  }
3162
- function unreadable_message3() {
3163
- return `A run record is here but could not be read; clear it with \`${END_COMMAND2}\` once you know no run is using it.`;
3314
+ function unreadable_lanes(lanes) {
3315
+ return lanes.filter((lane) => !lane.is_stranded && lane.seat === void 0);
3164
3316
  }
3165
- function unknown_message3() {
3166
- return "This repository\u2019s git directory could not be read, so no run record was established.";
3317
+ function find_lane(lanes, issue) {
3318
+ return lanes.find((lane) => lane.issue === issue);
3167
3319
  }
3168
- var run_carry = {
3169
- CARRY_MAX_AGE_HOURS,
3170
- CARRY_MAX_AGE_MS,
3171
- END_COMMAND: END_COMMAND2,
3172
- MAX_CUTS,
3173
- NO_OWNER,
3174
- RESUME_COMMAND,
3175
- adopt_carry,
3176
- apply_change: run_carry_change.apply_change,
3177
- begin_carry,
3178
- busy_message: busy_message2,
3179
- carry_path,
3180
- classify: classify4,
3181
- classify_claim,
3182
- count_refused_message,
3183
- is_at_cut_cap,
3184
- describe_carry,
3185
- end_carry,
3186
- expired_message,
3187
- mismatch_message,
3188
- fresh_carry,
3189
- is_count_refused,
3190
- is_expired: is_expired2,
3191
- is_foreign_live_owner,
3192
- is_owner_live,
3193
- lost_message,
3194
- nothing_to_resume_message,
3195
- owner_of,
3196
- parse_carry,
3197
- read_carry,
3198
- remaining_of,
3199
- replace_carry,
3200
- repository_directory,
3201
- retrospective_done_of,
3202
- standing_message,
3203
- unknown_message: unknown_message3,
3204
- unreadable_message: unreadable_message3
3320
+ async function find_open_lane(issue) {
3321
+ return find_lane(await list_lanes(), issue);
3322
+ }
3323
+ var lane_registry = {
3324
+ find_lane,
3325
+ find_open_lane,
3326
+ list_lanes,
3327
+ main_repository_root,
3328
+ parse_block,
3329
+ read_environment,
3330
+ unreadable_lanes,
3331
+ used_seats
3205
3332
  };
3206
3333
 
3207
3334
  // scripts/run/run-progress-clock.ts
@@ -3348,9 +3475,9 @@ var run_progress_clock = {
3348
3475
  // scripts/run/run-watcher-guard.ts
3349
3476
  var WATCHER_TICK_MS = 3e4;
3350
3477
  var TICK_MULTIPLIER = 3;
3351
- var MS_PER_SECOND2 = 1e3;
3478
+ var MS_PER_SECOND3 = 1e3;
3352
3479
  var STALE_THRESHOLD_MS = WATCHER_TICK_MS * TICK_MULTIPLIER;
3353
- var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND2;
3480
+ var STALE_THRESHOLD_S = STALE_THRESHOLD_MS / MS_PER_SECOND3;
3354
3481
  var STALE_NOTE = `Lane children are in-flight but the progress watcher has not pinged in the last ${String(STALE_THRESHOLD_S)}s \u2014 run \`pnpm josh run:progress --wait\` in the background before proceeding`;
3355
3482
  var OK_RESULT = { kind: "ok" };
3356
3483
  async function has_lanes_in_flight() {
@@ -3369,6 +3496,114 @@ var run_watcher_guard = {
3369
3496
  has_lanes_in_flight
3370
3497
  };
3371
3498
 
3499
+ // scripts/run/run-headless.ts
3500
+ var HEADLESS_ENV_KEY = "JOSH_RUN_HEADLESS";
3501
+ var HEADLESS_VALUE = "1";
3502
+ function environment() {
3503
+ return { [HEADLESS_ENV_KEY]: HEADLESS_VALUE };
3504
+ }
3505
+ function is_headless(source = process.env) {
3506
+ return source[HEADLESS_ENV_KEY] === HEADLESS_VALUE;
3507
+ }
3508
+ function is_driving(read2) {
3509
+ return read2?.kind === "carried" && read2.carry.is_handed_off !== true;
3510
+ }
3511
+ async function read_carry_here() {
3512
+ const repository2 = await run_carry.repository_directory();
3513
+ return repository2 === void 0 ? void 0 : run_carry.read_carry(run_carry.carry_path(repository2));
3514
+ }
3515
+ async function must_keep_waiting(source = process.env) {
3516
+ const is_candidate2 = is_headless(source) && lane_child_marker.marked_issue(source) === void 0;
3517
+ if (!is_candidate2 || !await run_watcher_guard.has_lanes_in_flight()) return false;
3518
+ return is_driving(await read_carry_here());
3519
+ }
3520
+ async function current_carry() {
3521
+ const read2 = await read_carry_here();
3522
+ return read2?.kind === "carried" ? read2.carry : void 0;
3523
+ }
3524
+ function is_owned_here(carry, ancestry) {
3525
+ return carry.owner_pid !== void 0 && ancestry().has(carry.owner_pid);
3526
+ }
3527
+ function driven_here(read2, ancestry) {
3528
+ if (read2?.kind !== "carried" || !is_driving(read2)) return void 0;
3529
+ return is_owned_here(read2.carry, ancestry) ? read2.carry : void 0;
3530
+ }
3531
+ async function driving_carry(source = process.env, ancestry = lane_reap.own_ancestry) {
3532
+ if (lane_child_marker.marked_issue(source) !== void 0) return void 0;
3533
+ return driven_here(await read_carry_here(), ancestry);
3534
+ }
3535
+ async function is_backlog_parent(source = process.env, ancestry = lane_reap.own_ancestry) {
3536
+ return await driving_carry(source, ancestry) !== void 0;
3537
+ }
3538
+ var run_headless = {
3539
+ HEADLESS_ENV_KEY,
3540
+ current_carry,
3541
+ driving_carry,
3542
+ environment,
3543
+ is_backlog_parent,
3544
+ is_headless,
3545
+ is_owned_here,
3546
+ must_keep_waiting
3547
+ };
3548
+
3549
+ // scripts/issue/issue-cite.ts
3550
+ var GITHUB_URL_PREFIX = "https://github.com/";
3551
+ var ISSUE_PATH = "/issues/";
3552
+ var SUMMARY_SEPARATOR = " \u2014 ";
3553
+ var REPO_QUALIFIED = /^([^\s/#]+\/[^\s/#]+)#(\d+)$/u;
3554
+ var BARE_NUMBER = /^#?(\d+)$/u;
3555
+ function issue_url(slug, number) {
3556
+ return `${GITHUB_URL_PREFIX}${slug}${ISSUE_PATH}${number}`;
3557
+ }
3558
+ function citation_line(slug, number, summary) {
3559
+ return `[#${number}](${issue_url(slug, number)})${SUMMARY_SEPARATOR}${summary}`;
3560
+ }
3561
+ function citer(slug, titles) {
3562
+ return function cite(number) {
3563
+ if (slug === void 0) return `#${number}`;
3564
+ const title = titles.get(number);
3565
+ return title === void 0 ? `[#${number}](${issue_url(slug, number)})` : citation_line(slug, number, title);
3566
+ };
3567
+ }
3568
+ function qualified_target(token) {
3569
+ const match = REPO_QUALIFIED.exec(token);
3570
+ if (match === null) return void 0;
3571
+ const [, repo = "", number = ""] = match;
3572
+ return { number, repo };
3573
+ }
3574
+ function bare_target(token, default_repo) {
3575
+ const match = BARE_NUMBER.exec(token);
3576
+ if (match === null) return void 0;
3577
+ const [, number = ""] = match;
3578
+ return { number, repo: default_repo };
3579
+ }
3580
+ function parse_target(token, default_repo) {
3581
+ return qualified_target(token) ?? bare_target(token, default_repo);
3582
+ }
3583
+ function label(target) {
3584
+ return target.repo === void 0 ? `#${target.number}` : `${target.repo}#${target.number}`;
3585
+ }
3586
+ function missing_line(target) {
3587
+ return `\u2716 ${label(target)}: issue does not resolve \u2014 check the number and the repository`;
3588
+ }
3589
+ function unreadable_line(target) {
3590
+ return `\u2716 ${label(target)}: could not be read \u2014 a rate limit, expired auth, or a dropped connection`;
3591
+ }
3592
+ function no_repo_line(target) {
3593
+ return `\u2716 ${label(target)}: could not read this repository \u2014 check gh auth and the origin remote`;
3594
+ }
3595
+ var issue_cite = {
3596
+ SUMMARY_SEPARATOR,
3597
+ citation_line,
3598
+ citer,
3599
+ issue_url,
3600
+ label,
3601
+ missing_line,
3602
+ no_repo_line,
3603
+ parse_target,
3604
+ unreadable_line
3605
+ };
3606
+
3372
3607
  // scripts/git/git-gh-api-path.ts
3373
3608
  var FULL_PAGE_QUERY = "?per_page=100";
3374
3609
  function repo_api_path(repo) {
@@ -3431,6 +3666,35 @@ var git_gh_api_path = {
3431
3666
  commit_status_api_path
3432
3667
  };
3433
3668
 
3669
+ // scripts/lib/error-message.ts
3670
+ function message_of(error) {
3671
+ return error instanceof Error ? error.message : String(error);
3672
+ }
3673
+ var error_text = { message_of };
3674
+
3675
+ // scripts/git/git-gh-exec.ts
3676
+ import { execa as execa5, execaSync as execaSync2 } from "execa";
3677
+
3678
+ // scripts/git/git-gh-check.ts
3679
+ import { execa as execa4 } from "execa";
3680
+ var GH_NOT_INSTALLED_MSG = "gh CLI is not installed. Install it from https://cli.github.com/";
3681
+ var gh_check = {};
3682
+ async function run_gh_check() {
3683
+ try {
3684
+ await execa4("gh", ["--version"]);
3685
+ } catch {
3686
+ delete gh_check.promise;
3687
+ throw new Error(GH_NOT_INSTALLED_MSG);
3688
+ }
3689
+ }
3690
+ async function check_gh_installed() {
3691
+ gh_check.promise ??= run_gh_check();
3692
+ await gh_check.promise;
3693
+ }
3694
+
3695
+ // scripts/git/git-gh-failure.ts
3696
+ import { z as z7 } from "zod";
3697
+
3434
3698
  // scripts/git/parse-json-array.ts
3435
3699
  var MALFORMED_JSON = /* @__PURE__ */ Symbol("malformed-json");
3436
3700
  function parse_json_loose(raw_json) {
@@ -3471,34 +3735,7 @@ var parse_json = {
3471
3735
  read_json_listing
3472
3736
  };
3473
3737
 
3474
- // scripts/lib/error-message.ts
3475
- function message_of(error) {
3476
- return error instanceof Error ? error.message : String(error);
3477
- }
3478
- var error_text = { message_of };
3479
-
3480
- // scripts/git/git-gh-exec.ts
3481
- import { execa as execa4, execaSync as execaSync2 } from "execa";
3482
-
3483
- // scripts/git/git-gh-check.ts
3484
- import { execa as execa3 } from "execa";
3485
- var GH_NOT_INSTALLED_MSG = "gh CLI is not installed. Install it from https://cli.github.com/";
3486
- var gh_check = {};
3487
- async function run_gh_check() {
3488
- try {
3489
- await execa3("gh", ["--version"]);
3490
- } catch {
3491
- delete gh_check.promise;
3492
- throw new Error(GH_NOT_INSTALLED_MSG);
3493
- }
3494
- }
3495
- async function check_gh_installed() {
3496
- gh_check.promise ??= run_gh_check();
3497
- await gh_check.promise;
3498
- }
3499
-
3500
3738
  // scripts/git/git-gh-failure.ts
3501
- import { z as z7 } from "zod";
3502
3739
  var error_document_schema = z7.object({ status: z7.union([z7.string(), z7.number()]).optional() }).catch({});
3503
3740
  var GH_FAILURE_FIELD = "gh_failure";
3504
3741
  function has_gh_failure(error) {
@@ -3568,7 +3805,7 @@ async function exec_gh_command(arguments_, timeout_ms, cwd) {
3568
3805
  await check_gh_installed();
3569
3806
  const options = to_spawn_options(timeout_ms, cwd);
3570
3807
  try {
3571
- const { stdout } = await execa4("gh", arguments_, options);
3808
+ const { stdout } = await execa5("gh", arguments_, options);
3572
3809
  return stdout.trimEnd();
3573
3810
  } catch (error) {
3574
3811
  throw to_gh_error(error);
@@ -3578,7 +3815,7 @@ async function exec_gh_command_with_stdin(input) {
3578
3815
  await check_gh_installed();
3579
3816
  const options = { input: input.stdin_body, ...to_spawn_options(input.timeout_ms, input.cwd) };
3580
3817
  try {
3581
- const { stdout } = await execa4("gh", input.args, options);
3818
+ const { stdout } = await execa5("gh", input.args, options);
3582
3819
  return stdout.trimEnd();
3583
3820
  } catch (error) {
3584
3821
  throw to_gh_error(error);
@@ -3596,7 +3833,7 @@ async function exec_gh_api_status(path4, timeout_ms) {
3596
3833
  const options = to_spawn_options(timeout_ms);
3597
3834
  try {
3598
3835
  await check_gh_installed();
3599
- const { stdout } = await execa4("gh", ["api", "--include", "--silent", path4], options);
3836
+ const { stdout } = await execa5("gh", ["api", "--include", "--silent", path4], options);
3600
3837
  return parse_status_line(stdout);
3601
3838
  } catch (error) {
3602
3839
  return status_of_failure(error);
@@ -3677,6 +3914,7 @@ var git_gh_exec = {
3677
3914
  export {
3678
3915
  shell_segments,
3679
3916
  bash_triggers,
3917
+ issue_cite,
3680
3918
  run_progress_clock,
3681
3919
  tail_commands,
3682
3920
  filing_cap,
@@ -3686,7 +3924,6 @@ export {
3686
3924
  agent_role_profile,
3687
3925
  backlog_budget,
3688
3926
  git_command,
3689
- lane_child_invocation,
3690
3927
  run_hold,
3691
3928
  run_cut,
3692
3929
  run_tail,
@@ -3706,6 +3943,12 @@ export {
3706
3943
  lane_park,
3707
3944
  repo_origin,
3708
3945
  repo_party,
3946
+ process_identity,
3947
+ run_invocation,
3948
+ run_carry,
3949
+ lane_registry,
3950
+ run_watcher_guard,
3951
+ run_headless,
3709
3952
  FULL_PAGE_QUERY,
3710
3953
  git_gh_api_path,
3711
3954
  error_text,
@@ -3714,10 +3957,5 @@ export {
3714
3957
  parse_json_object_safe,
3715
3958
  gh_failure,
3716
3959
  has_stderr_field,
3717
- git_gh_exec,
3718
- process_identity,
3719
- lane_registry,
3720
- run_invocation,
3721
- run_carry,
3722
- run_watcher_guard
3960
+ git_gh_exec
3723
3961
  };