@joshuafolkken/kit 1.929.0 → 1.953.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 (83) hide show
  1. package/.claude/skills/workflow-commands/background-commands.md +6 -0
  2. package/.claude/skills/workflow-commands/backlogrun-child.md +1 -1
  3. package/.claude/skills/workflow-commands/backlogrun-lanes.md +4 -3
  4. package/.claude/skills/workflow-commands/observation-filing.md +3 -3
  5. package/.claude/skills/workflow-commands/prerequisite.md +1 -1
  6. package/.github/workflows/ci.yml +26 -8
  7. package/.github/workflows/pr-classification.yml +76 -0
  8. package/README.md +44 -22
  9. package/dist/hooks/batch-guard.js +4 -4
  10. package/dist/hooks/{chunk-GQ3EVWHY.js → chunk-452OKRQX.js} +421 -195
  11. package/dist/hooks/{chunk-2PGRQ62K.js → chunk-EYFJ74MJ.js} +11 -6
  12. package/dist/hooks/{chunk-LCJZE42I.js → chunk-IA7WC4NS.js} +1 -1
  13. package/dist/hooks/{chunk-BNFG2IFK.js → chunk-IBFC25IX.js} +4 -4
  14. package/dist/hooks/{chunk-I3JP3D4A.js → chunk-M4U4YNRE.js} +3 -3
  15. package/dist/hooks/{chunk-NJZSO7BK.js → chunk-NKVLBB65.js} +3 -3
  16. package/dist/hooks/{chunk-SXPTRFPG.js → chunk-O3PEDF3M.js} +1 -1
  17. package/dist/hooks/{chunk-7XQ6TK3R.js → chunk-OM5VDM6F.js} +2 -2
  18. package/dist/hooks/{chunk-ROULPSQC.js → chunk-V7RH56HU.js} +2 -2
  19. package/dist/hooks/{chunk-YO43HEPY.js → chunk-Z5TVZ4AP.js} +237 -235
  20. package/dist/hooks/codex-hook-adapter.js +10 -10
  21. package/dist/hooks/duplicate-read-guard.js +5 -5
  22. package/dist/hooks/format-edited.js +2 -2
  23. package/dist/hooks/investigation-guard.js +5 -5
  24. package/dist/hooks/pretool-guard.js +9 -9
  25. package/dist/hooks/stop-guard.js +9 -3
  26. package/dist/josh.js +11 -6
  27. package/dist/version/index.js +7 -4
  28. package/package.json +13 -1
  29. package/prompts/collaboration-workflow/issue-template.md +1 -1
  30. package/scripts/ci/pr-classification.ts +27 -6
  31. package/scripts/document/document-byte-budget.ts +1 -1
  32. package/scripts/gate/hook-gate-reuse.ts +1 -1
  33. package/scripts/gate/project-checks.ts +45 -15
  34. package/scripts/gate/type-check-command.ts +43 -0
  35. package/scripts/git/constants.ts +4 -0
  36. package/scripts/git/git-command.ts +11 -5
  37. package/scripts/git/git-gh-pr-rollup.ts +56 -16
  38. package/scripts/git/git-staging.ts +6 -4
  39. package/scripts/git/git-stash.ts +73 -1
  40. package/scripts/git/git-worktree.ts +35 -4
  41. package/scripts/git/repository-lock.ts +138 -0
  42. package/scripts/git/stash-orphans.ts +24 -2
  43. package/scripts/git/stash-sweep-lock.ts +30 -0
  44. package/scripts/hooks/stop-guard.ts +2 -0
  45. package/scripts/init/init-actions.ts +3 -2
  46. package/scripts/init/init-logic-migrate.ts +10 -1
  47. package/scripts/init/init-logic-templates.ts +27 -7
  48. package/scripts/init/init-logic-workspace.ts +52 -1
  49. package/scripts/init/init-logic.ts +6 -1
  50. package/scripts/init/init.ts +60 -19
  51. package/scripts/issue/issue-merged.ts +75 -0
  52. package/scripts/issue/measure-rerun-cli.ts +2 -2
  53. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
  54. package/scripts/josh/josh-command-map.ts +1 -0
  55. package/scripts/josh/josh-command-types.ts +0 -3
  56. package/scripts/josh/josh-commands-ai.ts +6 -0
  57. package/scripts/josh/josh-commands-development.ts +5 -5
  58. package/scripts/lint/lint-parallel.ts +19 -18
  59. package/scripts/observations/observation-ledger-home.ts +22 -2
  60. package/scripts/observations/observation-ledger-line.ts +11 -0
  61. package/scripts/observations/observation-ledger-migrate.ts +146 -0
  62. package/scripts/observations/observation-ledger.ts +51 -3
  63. package/scripts/observations/observations-flush.ts +38 -7
  64. package/scripts/review/review-finding-ledger.ts +1 -1
  65. package/scripts/review/review-record-cli.ts +1 -7
  66. package/scripts/rules/delivered-rules.ts +10 -0
  67. package/scripts/rules/lane-background.ts +111 -0
  68. package/scripts/rules/stop-rules.ts +15 -2
  69. package/scripts/run/run-carry-flush.ts +2 -7
  70. package/scripts/run/run-hold-cli.ts +10 -2
  71. package/scripts/run/run-tidy-cli.ts +50 -0
  72. package/scripts/run/run-tidy-lanes.ts +110 -0
  73. package/scripts/run/run-tidy-stashes.ts +160 -0
  74. package/scripts/run/run-tidy.ts +101 -0
  75. package/scripts/safe-chain/ci-installer-pin.ts +90 -0
  76. package/scripts/safe-chain/preinstall-command.ts +39 -0
  77. package/scripts/safe-chain/project-config.ts +55 -10
  78. package/scripts/sync/sync.ts +8 -3
  79. package/scripts/test/pilot-files.ts +0 -1
  80. package/scripts/version/preinstall-version-update.ts +2 -0
  81. package/scripts/version/release-age.ts +11 -6
  82. package/templates/workflows/ci.yml +21 -7
  83. package/tsconfig/base.json +4 -1
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  lane_child_marker,
3
3
  run_issue_number
4
- } from "./chunk-SXPTRFPG.js";
4
+ } from "./chunk-O3PEDF3M.js";
5
5
  import {
6
6
  PLATFORM_TEMP_ROOT,
7
7
  cost_blocks,
8
8
  cost_usage,
9
+ json_value,
9
10
  lane_paths,
10
11
  stamp_file,
11
12
  time_density_hook,
12
13
  time_shell,
13
14
  time_transcript_line
14
- } from "./chunk-2PGRQ62K.js";
15
+ } from "./chunk-EYFJ74MJ.js";
15
16
  import {
16
17
  ENV_FILE_NAME,
17
18
  LANE_SEAT_KEY,
@@ -111,6 +112,7 @@ var filing_cap = {
111
112
  import { platform } from "node:os";
112
113
  var GIT_COMMAND_UNIX = "/usr/bin/git";
113
114
  var PORCELAIN_FLAG = "--porcelain";
115
+ var UNTRACKED_FILES_FLAG = "--untracked-files=all";
114
116
  function get_git_command() {
115
117
  if (platform() === "win32") {
116
118
  return String.raw`"C:\Program Files\Git\cmd\git.exe"`;
@@ -242,7 +244,7 @@ async function branch() {
242
244
  return await git_spawn.read(["rev-parse", "--abbrev-ref", "HEAD"]);
243
245
  }
244
246
  async function status() {
245
- return await git_spawn.read(["status", PORCELAIN_FLAG, "--untracked-files=normal"]);
247
+ return await git_spawn.read(["status", PORCELAIN_FLAG, UNTRACKED_FILES_FLAG]);
246
248
  }
247
249
  async function repository_root() {
248
250
  return await git_spawn.read(["rev-parse", "--show-toplevel"]);
@@ -1516,6 +1518,112 @@ var run_cut = {
1516
1518
  worktree_git_directory_sync
1517
1519
  };
1518
1520
 
1521
+ // scripts/rules/shell-segments.ts
1522
+ var SEGMENT_SEPARATOR = /&&|\|\||;|\n/u;
1523
+ function segments_of(command) {
1524
+ return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim());
1525
+ }
1526
+ function is_josh_command(segment, names) {
1527
+ const named = time_shell.josh_command_of(segment);
1528
+ if (!named.startsWith(time_shell.JOSH_PREFIX)) return false;
1529
+ return names.has(named.slice(time_shell.JOSH_PREFIX.length));
1530
+ }
1531
+ var shell_segments = {
1532
+ SEGMENT_SEPARATOR,
1533
+ is_josh_command,
1534
+ segments_of
1535
+ };
1536
+
1537
+ // scripts/rules/run-tail.ts
1538
+ var SEGMENT_PREFIX = String.raw`(?:\(\s*)?(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*(?:npx\s+|pnpm\s+(?:exec\s+|run\s+)?)?`;
1539
+ var WORD_END = String.raw`(?=\s|$|\))`;
1540
+ var PUSH_STEP_SEGMENT = new RegExp(String.raw`^${SEGMENT_PREFIX}josh\s+(?:git|g)${WORD_END}`, "u");
1541
+ var CONFIRMED_FLAG = new RegExp(String.raw`(?:^|\s)(?:-y|--yes)${WORD_END}`, "u");
1542
+ var SKIPS_THE_PUSH = new RegExp(String.raw`(?:^|\s)--skip-push${WORD_END}`, "u");
1543
+ var BACKGROUND_KEY = "run_in_background";
1544
+ var QUOTED_SPAN = /"[^"]*"|'[^']*'/gu;
1545
+ function flags_of(segment) {
1546
+ return segment.replaceAll(QUOTED_SPAN, " ");
1547
+ }
1548
+ function is_push_step_segment(segment) {
1549
+ if (!PUSH_STEP_SEGMENT.test(segment)) return false;
1550
+ const flags = flags_of(segment);
1551
+ if (SKIPS_THE_PUSH.test(flags)) return false;
1552
+ return CONFIRMED_FLAG.test(flags);
1553
+ }
1554
+ function is_push_step(command) {
1555
+ return shell_segments.segments_of(command).some((segment) => is_push_step_segment(segment));
1556
+ }
1557
+ function is_backgrounded(input) {
1558
+ if (!json_value.is_record(input)) return false;
1559
+ return input[BACKGROUND_KEY] === true;
1560
+ }
1561
+ function is_push_step_call(call) {
1562
+ if (call.name !== cost_blocks.BASH_TOOL) return false;
1563
+ return is_push_step(time_shell.bash_command(call.input));
1564
+ }
1565
+ function is_backgrounded_push_step(call) {
1566
+ if (!is_backgrounded(call.input)) return false;
1567
+ return is_push_step_call(call);
1568
+ }
1569
+ function is_foreground_push_step(call) {
1570
+ if (is_backgrounded(call.input)) return false;
1571
+ return is_push_step_call(call);
1572
+ }
1573
+ function decide3() {
1574
+ return true;
1575
+ }
1576
+ var RUN_TAIL_REASON = '\u26D4 run tail: this issues the commit-push-PR step in the foreground, where the harness decides when to stop waiting. Reissue it with `run_in_background` set. In `fullrun #1501` this call was given a 900-second timeout, the harness detached it at its own 600-second cap, and nothing read the output for a further 6m28s \u2014 45m03s of wall clock on about 17 minutes of work (joshuafolkken/kit#1510). **Never give a foreground call a timeout above the harness cap**: the cap decides, not the number. A backgrounded call re-invokes you when it exits, so the completion notification is what resumes the run. **When it arrives, the turn that reads it goes straight through any branch-2 filing and `pnpm josh epic:bundle` to `pnpm josh followup` \u2014 the turn never ends at the push.** CI went green 6m06s before the merge started in that run, and it started only because the person asked; joshuafolkken/kit#1333 had already settled that the merge is issued in the turn the review closes. While the push runs, do the work that writes nothing \u2014 drafting the completion body to a file, deciding the branch-2 disposition. `pnpm josh followup` itself stays in the **foreground** \u2014 nearly every step after it reads its result. But a tail does follow the merge, measured at 3.0 min and 5.9% of a run (joshuafolkken/kit#1462), so empty it beforehand: the steps that read the merge result stay after `followup`, and the rest are composed in the turn that issues it, bar the one exception background-commands.md names. The procedure is `.claude/skills/workflow-commands/background-commands.md`, "A command that can take minutes is issued in the background". **This rule fires on every foreground push, not once per run**, so reissuing the same call in the foreground will be refused again.';
1577
+ var run_tail = {
1578
+ RUN_TAIL_REASON,
1579
+ decide: decide3,
1580
+ is_backgrounded_push_step,
1581
+ is_foreground_push_step,
1582
+ is_push_step,
1583
+ is_push_step_call
1584
+ };
1585
+
1586
+ // scripts/rules/lane-background.ts
1587
+ var LONG_RUNNING_COMMANDS = new Set(
1588
+ ["gate", "git", "followup"].map((name) => `${time_shell.JOSH_PREFIX}${name}`)
1589
+ );
1590
+ var BACKGROUND_KEY2 = "run_in_background";
1591
+ function is_backgrounded2(input) {
1592
+ return json_value.is_record(input) && input[BACKGROUND_KEY2] === true;
1593
+ }
1594
+ function runs_long_command(command) {
1595
+ return time_shell.josh_commands_of(command).some((name) => LONG_RUNNING_COMMANDS.has(name));
1596
+ }
1597
+ function is_candidate(call) {
1598
+ if (run_tail.is_push_step_call(call)) return true;
1599
+ if (call.name !== cost_blocks.BASH_TOOL || !is_backgrounded2(call.input)) return false;
1600
+ return runs_long_command(time_shell.bash_command(call.input));
1601
+ }
1602
+ function is_background_long_run(call, is_lane_child = () => lane_child_marker.is_child_of(process.cwd())) {
1603
+ return is_candidate(call) && is_lane_child();
1604
+ }
1605
+ function pending_background_ids(tail) {
1606
+ const lines = tail.split("\n").map((line) => time_transcript_line.parse_line(line));
1607
+ const finished = new Set(lines.map((line) => line?.finished_background ?? ""));
1608
+ const launched = lines.flatMap((line) => line?.blocks.map((block) => block.background_id) ?? []);
1609
+ return launched.filter((id) => id !== "" && !finished.has(id));
1610
+ }
1611
+ var LANE_BACKGROUND_REASON = '\u26D4 lane background: this session is a dispatched lane child (`JOSH_LANE_CHILD`), a headless `claude -p` process whose background Bash tasks are killed when the turn ends \u2014 so a backgrounded `josh gate` / `josh git` / `josh followup` never re-invokes you; it dies at `exit code 143` (joshuafolkken/kit#2704, measured on the #2606 child: implementation, review and live verification done, the run parked instead of merged). Hand the gate-to-merge region to the detached supervisor instead: `pnpm josh ship --detach --review "<title> #<N>"` (add `--cite <N>`), then end the turn on `launched` / `busy` \u2014 `.claude/skills/workflow-commands/chain-rule.md` step 0 is the single source. A single check you must read before continuing runs in the foreground within the tool timeout; the commit-push step does not \u2014 in a child it too belongs to the detached ship, in either spelling. This rule fires on every occurrence, not once per run.';
1612
+ var LANE_BACKGROUND_STOP_REASON = "\u26D4 lane background stop: this is a dispatched lane child (`JOSH_LANE_CHILD`) and a background task it launched has not finished \u2014 ending this headless turn kills it, and nothing re-invokes you afterwards (joshuafolkken/kit#2704). Do not send a notification and do not stop: wait for the task in the foreground and act on its result, or re-issue the work through `pnpm josh ship --detach`, whose supervisor outlives the turn (`.claude/skills/workflow-commands/chain-rule.md` step 0). Do not repeat your previous reply.";
1613
+ var ROW2 = {
1614
+ id: "lane-background",
1615
+ is_trigger: (call) => is_background_long_run(call),
1616
+ reason: LANE_BACKGROUND_REASON,
1617
+ decide: () => true
1618
+ };
1619
+ var lane_background = {
1620
+ LANE_BACKGROUND_REASON,
1621
+ LANE_BACKGROUND_STOP_REASON,
1622
+ ROW: ROW2,
1623
+ is_background_long_run,
1624
+ pending_background_ids
1625
+ };
1626
+
1519
1627
  // scripts/git/issue-labels.ts
1520
1628
  var BUG_LABEL = "bug";
1521
1629
  var ENHANCEMENT_LABEL = "enhancement";
@@ -1535,22 +1643,6 @@ function has_label_name(labels, wanted) {
1535
1643
  return label_name_of(labels, wanted) !== void 0;
1536
1644
  }
1537
1645
 
1538
- // scripts/rules/shell-segments.ts
1539
- var SEGMENT_SEPARATOR = /&&|\|\||;|\n/u;
1540
- function segments_of(command) {
1541
- return command.split(SEGMENT_SEPARATOR).map((segment) => segment.trim());
1542
- }
1543
- function is_josh_command(segment, names) {
1544
- const named = time_shell.josh_command_of(segment);
1545
- if (!named.startsWith(time_shell.JOSH_PREFIX)) return false;
1546
- return names.has(named.slice(time_shell.JOSH_PREFIX.length));
1547
- }
1548
- var shell_segments = {
1549
- SEGMENT_SEPARATOR,
1550
- is_josh_command,
1551
- segments_of
1552
- };
1553
-
1554
1646
  // scripts/rules/lane-park.ts
1555
1647
  var NOTIFY_COMMANDS = /* @__PURE__ */ new Set(["notify"]);
1556
1648
  var CONFIRMATION_TASK_TYPE = /(?:^|\s)--task-type[=\s]+["']?confirmation\b/u;
@@ -1814,8 +1906,8 @@ function current_owner(repository_path = process.cwd()) {
1814
1906
  var repo_party = { FIRST_PARTY, THIRD_PARTY, UNKNOWN, classify: classify3, current_owner, target_owner };
1815
1907
 
1816
1908
  // scripts/lane/lane-registry.ts
1817
- import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
1818
- import path2 from "node:path";
1909
+ import { existsSync as existsSync2, readFileSync as readFileSync4 } from "node:fs";
1910
+ import path3 from "node:path";
1819
1911
 
1820
1912
  // scripts/git/git-ls-remote.ts
1821
1913
  var LS_REMOTE_COMMAND = "ls-remote";
@@ -1826,23 +1918,323 @@ function ls_remote_branch_arguments(branch_name) {
1826
1918
  return [LS_REMOTE_COMMAND, HEADS_FLAG, ORIGIN_REMOTE, `${HEADS_REF_PREFIX}${branch_name}`];
1827
1919
  }
1828
1920
 
1921
+ // scripts/git/repository-lock.ts
1922
+ import { randomUUID as randomUUID2 } from "node:crypto";
1923
+ import { linkSync, renameSync, rmSync as rmSync2 } from "node:fs";
1924
+ import { setTimeout as sleep } from "node:timers/promises";
1925
+
1926
+ // scripts/josh/process-identity.ts
1927
+ import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
1928
+ import { randomUUID } from "node:crypto";
1929
+ import { readFileSync as readFileSync3, rmSync } from "node:fs";
1930
+ import { createServer } from "node:net";
1931
+ import path2 from "node:path";
1932
+ var PS_COMMAND = "/bin/ps";
1933
+ var FORMAT_FLAG = "-o";
1934
+ var START_TIME_FORMAT = "lstart=";
1935
+ var PID_FLAG = "-p";
1936
+ var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
1937
+ var PROBE_TIMEOUT_MS = 2e3;
1938
+ var LIVENESS_SIGNAL = 0;
1939
+ var LOWEST_REAL_PID = 1;
1940
+ var PROC_START_FIELD_INDEX = 19;
1941
+ var BEACON_SCHEME = "socket:";
1942
+ var BEACON_SUFFIX = ".sock";
1943
+ var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
1944
+ var BEACON_ID_LENGTH = 36;
1945
+ var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
1946
+ var WINDOWS_PLATFORM = "win32";
1947
+ var SOCKET_PROBE_SOURCE = "const net=require('node:net');const socket=net.createConnection(process.argv[1]);socket.once('connect',()=>process.exit(0));socket.once('error',()=>process.exit(1));socket.setTimeout(1000,()=>process.exit(1));";
1948
+ var beacons = /* @__PURE__ */ new Map();
1949
+ var exit_cleanup = { is_registered: false };
1950
+ function is_live_pid(pid) {
1951
+ if (pid < LOWEST_REAL_PID) return false;
1952
+ try {
1953
+ process.kill(pid, LIVENESS_SIGNAL);
1954
+ return true;
1955
+ } catch {
1956
+ return false;
1957
+ }
1958
+ }
1959
+ function read_proc_start(pid) {
1960
+ try {
1961
+ const stat = readFileSync3(`/proc/${String(pid)}/stat`, "utf8");
1962
+ const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
1963
+ const start = fields[PROC_START_FIELD_INDEX];
1964
+ return start === void 0 ? void 0 : `proc:${start}`;
1965
+ } catch {
1966
+ return void 0;
1967
+ }
1968
+ }
1969
+ function read_ps_start(pid) {
1970
+ try {
1971
+ const printed = execFileSync2(
1972
+ PS_COMMAND,
1973
+ [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
1974
+ {
1975
+ encoding: "utf8",
1976
+ timeout: PROBE_TIMEOUT_MS,
1977
+ stdio: ["ignore", "pipe", "ignore"],
1978
+ env: PROBE_ENVIRONMENT
1979
+ }
1980
+ ).trim();
1981
+ return printed.length > 0 ? printed : void 0;
1982
+ } catch {
1983
+ return void 0;
1984
+ }
1985
+ }
1986
+ var START_PROBES = { read_proc_start, read_ps_start };
1987
+ function safe_probe(probe, pid) {
1988
+ try {
1989
+ return probe(pid);
1990
+ } catch {
1991
+ return void 0;
1992
+ }
1993
+ }
1994
+ function read_start(pid, probes = START_PROBES) {
1995
+ return safe_probe(probes.read_proc_start, pid) ?? safe_probe(probes.read_ps_start, pid);
1996
+ }
1997
+ function close_beacon(token) {
1998
+ const server = beacons.get(token);
1999
+ if (server === void 0) return;
2000
+ beacons.delete(token);
2001
+ server.close();
2002
+ rmSync(token.slice(BEACON_SCHEME.length), { force: true });
2003
+ }
2004
+ function close_beacons() {
2005
+ for (const token of beacons.keys()) close_beacon(token);
2006
+ }
2007
+ function ensure_exit_cleanup() {
2008
+ if (exit_cleanup.is_registered) return;
2009
+ exit_cleanup.is_registered = true;
2010
+ process.once("exit", close_beacons);
2011
+ }
2012
+ function ignore_beacon_error() {
2013
+ }
2014
+ function bind_beacon(target) {
2015
+ return createServer().on("error", ignore_beacon_error).listen(target);
2016
+ }
2017
+ function try_bind_beacon(bind, target) {
2018
+ try {
2019
+ const server = bind(target);
2020
+ return server.listening ? server : void 0;
2021
+ } catch {
2022
+ return void 0;
2023
+ }
2024
+ }
2025
+ function open_beacon(bind = bind_beacon) {
2026
+ const target = path2.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
2027
+ const token = `${BEACON_SCHEME}${target}`;
2028
+ const server = try_bind_beacon(bind, target);
2029
+ if (server === void 0) return void 0;
2030
+ server.unref();
2031
+ beacons.set(token, server);
2032
+ ensure_exit_cleanup();
2033
+ return token;
2034
+ }
2035
+ function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
2036
+ const start = read_start(process.pid, probes);
2037
+ if (start !== void 0 || platform2 === WINDOWS_PLATFORM) return start;
2038
+ return open_beacon();
2039
+ }
2040
+ function is_valid_beacon_name(name) {
2041
+ if (!name.startsWith(BEACON_PREFIX) || !name.endsWith(BEACON_SUFFIX)) return false;
2042
+ const id = name.slice(BEACON_PREFIX.length, -BEACON_SUFFIX.length);
2043
+ return id.length === BEACON_ID_LENGTH && BEACON_ID_PATTERN.test(id);
2044
+ }
2045
+ function beacon_target(token) {
2046
+ if (!token.startsWith(BEACON_SCHEME)) return void 0;
2047
+ const target = token.slice(BEACON_SCHEME.length);
2048
+ if (path2.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
2049
+ return is_valid_beacon_name(path2.basename(target)) ? target : void 0;
2050
+ }
2051
+ function is_live_beacon(target) {
2052
+ return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
2053
+ stdio: "ignore",
2054
+ timeout: PROBE_TIMEOUT_MS
2055
+ }).status === 0;
2056
+ }
2057
+ function has_matching_start(pid, recorded_start, read2) {
2058
+ const observed = read2(pid);
2059
+ return observed === void 0 ? void 0 : observed === recorded_start;
2060
+ }
2061
+ function matches_recorded_start(pid, recorded_start, read2) {
2062
+ const target = beacon_target(recorded_start);
2063
+ return target === void 0 ? has_matching_start(pid, recorded_start, read2) : is_live_beacon(target);
2064
+ }
2065
+ var own_probe = /* @__PURE__ */ new Map();
2066
+ function own_start() {
2067
+ if (!own_probe.has(process.pid)) own_probe.set(process.pid, resolve_own_start());
2068
+ return own_probe.get(process.pid);
2069
+ }
2070
+ function own_fields() {
2071
+ const start = own_start();
2072
+ return { pid: process.pid, ...start !== void 0 && { process_start: start } };
2073
+ }
2074
+ function is_same_process(pid, recorded_start, read2 = read_start) {
2075
+ if (pid === void 0 || !is_live_pid(pid)) return false;
2076
+ if (recorded_start === void 0) return void 0;
2077
+ return matches_recorded_start(pid, recorded_start, read2);
2078
+ }
2079
+ function is_own_process(pid, recorded_start) {
2080
+ return pid === process.pid && recorded_start === own_start();
2081
+ }
2082
+ var process_identity = {
2083
+ close_beacon,
2084
+ is_live_pid,
2085
+ is_own_process,
2086
+ is_same_process,
2087
+ open_beacon,
2088
+ own_fields,
2089
+ own_start,
2090
+ read_start,
2091
+ resolve_own_start
2092
+ };
2093
+
2094
+ // scripts/git/git-common-directory.ts
2095
+ import { execFileSync as execFileSync3 } from "node:child_process";
2096
+ var OWN_GIT_DIRECTORY = 0;
2097
+ var COMMON_GIT_DIRECTORY = 1;
2098
+ var PROBE_TIMEOUT_MS2 = 2e3;
2099
+ function read_directories(cwd) {
2100
+ const git = git_utilities.get_git_command_for_spawn();
2101
+ const output = execFileSync3(git, [...git_command.GIT_DIRECTORY_ARGUMENTS], {
2102
+ cwd,
2103
+ encoding: "utf8",
2104
+ stdio: ["ignore", "pipe", "ignore"],
2105
+ timeout: PROBE_TIMEOUT_MS2
2106
+ });
2107
+ return output.trim().split("\n");
2108
+ }
2109
+ function select_linked(directories) {
2110
+ const own = directories[OWN_GIT_DIRECTORY];
2111
+ const common = directories[COMMON_GIT_DIRECTORY];
2112
+ return own !== void 0 && common !== void 0 && own !== common ? common : void 0;
2113
+ }
2114
+ function resolve2(cwd) {
2115
+ try {
2116
+ return select_linked(read_directories(cwd));
2117
+ } catch {
2118
+ return void 0;
2119
+ }
2120
+ }
2121
+ function repository(cwd) {
2122
+ try {
2123
+ return read_directories(cwd)[COMMON_GIT_DIRECTORY];
2124
+ } catch {
2125
+ return void 0;
2126
+ }
2127
+ }
2128
+ var git_common_directory = { repository, resolve: resolve2, select_linked };
2129
+
2130
+ // scripts/git/repository-lock.ts
2131
+ var POLL_INTERVAL_MS = 200;
2132
+ function lock_path(prefix, cwd = process.cwd()) {
2133
+ return stamp_file.stamp_path(prefix, git_common_directory.repository(cwd) ?? cwd);
2134
+ }
2135
+ function parse_owner(raw) {
2136
+ if (raw === void 0) return void 0;
2137
+ try {
2138
+ return JSON.parse(raw);
2139
+ } catch {
2140
+ return void 0;
2141
+ }
2142
+ }
2143
+ function read_owner(target) {
2144
+ return parse_owner(stamp_file.read_stamp_text(target));
2145
+ }
2146
+ function is_owner_gone(owner) {
2147
+ return process_identity.is_same_process(owner.pid, owner.process_start) === false;
2148
+ }
2149
+ function restore(aside, target) {
2150
+ try {
2151
+ linkSync(aside, target);
2152
+ } catch {
2153
+ }
2154
+ }
2155
+ function stale_record(target, is_gone2) {
2156
+ const raw = stamp_file.read_stamp_text(target);
2157
+ const owner = parse_owner(raw);
2158
+ return owner !== void 0 && is_gone2(owner) ? raw : void 0;
2159
+ }
2160
+ function move_aside(target) {
2161
+ const aside = `${target}.${String(process.pid)}.${randomUUID2()}`;
2162
+ try {
2163
+ renameSync(target, aside);
2164
+ return aside;
2165
+ } catch {
2166
+ return void 0;
2167
+ }
2168
+ }
2169
+ function clear_stale(target, is_gone2 = is_owner_gone) {
2170
+ const raw = stale_record(target, is_gone2);
2171
+ if (raw === void 0) return;
2172
+ const aside = move_aside(target);
2173
+ if (aside === void 0) return;
2174
+ if (stamp_file.read_stamp_text(aside) !== raw) restore(aside, target);
2175
+ rmSync2(aside, { force: true });
2176
+ }
2177
+ function claim(target) {
2178
+ clear_stale(target);
2179
+ return stamp_file.create_stamp(target, process_identity.own_fields());
2180
+ }
2181
+ function release(target) {
2182
+ const owner = read_owner(target);
2183
+ if (owner !== void 0 && process_identity.is_own_process(owner.pid, owner.process_start)) {
2184
+ stamp_file.remove_stamp(target);
2185
+ }
2186
+ }
2187
+ async function acquire(target, max_wait_ms) {
2188
+ const deadline = Date.now() + max_wait_ms;
2189
+ while (!claim(target)) {
2190
+ if (Date.now() >= deadline) return false;
2191
+ await sleep(POLL_INTERVAL_MS);
2192
+ }
2193
+ return true;
2194
+ }
2195
+ async function with_lock(work, target, max_wait_ms) {
2196
+ if (!await acquire(target, max_wait_ms)) return void 0;
2197
+ try {
2198
+ return await work();
2199
+ } finally {
2200
+ release(target);
2201
+ }
2202
+ }
2203
+ var repository_lock = { clear_stale, lock_path, with_lock };
2204
+
1829
2205
  // scripts/git/git-worktree.ts
1830
2206
  var WORKTREE = "worktree";
2207
+ var WORKTREE_LOCK_PREFIX = "josh-worktree-lock-";
2208
+ var WORKTREE_LOCK_MAX_WAIT_MS = 12e4;
2209
+ async function locked(arguments_) {
2210
+ const target = repository_lock.lock_path(WORKTREE_LOCK_PREFIX);
2211
+ const output = await repository_lock.with_lock(
2212
+ async () => await git_spawn.read(arguments_),
2213
+ target,
2214
+ WORKTREE_LOCK_MAX_WAIT_MS
2215
+ );
2216
+ if (output === void 0) {
2217
+ throw new Error(
2218
+ `git ${arguments_.join(" ")}: timed out waiting for the work-tree lock ${target}`
2219
+ );
2220
+ }
2221
+ return output;
2222
+ }
1831
2223
  async function worktree_list() {
1832
2224
  return await git_spawn.read([WORKTREE, "list", PORCELAIN_FLAG]);
1833
2225
  }
1834
2226
  async function worktree_add(directory, branch_name, start_point) {
1835
2227
  const flags = start_point === void 0 ? ["add"] : ["add", "--no-track", "-b", branch_name];
1836
- return await git_spawn.read([WORKTREE, ...flags, directory, start_point ?? branch_name]);
2228
+ return await locked([WORKTREE, ...flags, directory, start_point ?? branch_name]);
1837
2229
  }
1838
2230
  async function worktree_add_detached(directory, commit2) {
1839
- return await git_spawn.read([WORKTREE, "add", "--detach", directory, commit2]);
2231
+ return await locked([WORKTREE, "add", "--detach", directory, commit2]);
1840
2232
  }
1841
2233
  async function worktree_remove(directory) {
1842
- return await git_spawn.read([WORKTREE, "remove", "--force", directory]);
2234
+ return await locked([WORKTREE, "remove", "--force", directory]);
1843
2235
  }
1844
2236
  async function worktree_prune() {
1845
- return await git_spawn.read([WORKTREE, "prune"]);
2237
+ return await locked([WORKTREE, "prune"]);
1846
2238
  }
1847
2239
  async function ls_remote_branch(branch_name) {
1848
2240
  return await git_spawn.read(ls_remote_branch_arguments(branch_name));
@@ -2005,7 +2397,7 @@ function held_seat(content) {
2005
2397
  }
2006
2398
  function read_environment(directory) {
2007
2399
  try {
2008
- return readFileSync3(path2.join(directory, ENV_FILE_NAME), "utf8");
2400
+ return readFileSync4(path3.join(directory, ENV_FILE_NAME), "utf8");
2009
2401
  } catch {
2010
2402
  return void 0;
2011
2403
  }
@@ -2371,174 +2763,6 @@ var run_invocation = {
2371
2763
  rebuild
2372
2764
  };
2373
2765
 
2374
- // scripts/josh/process-identity.ts
2375
- import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
2376
- import { randomUUID } from "node:crypto";
2377
- import { readFileSync as readFileSync4, rmSync } from "node:fs";
2378
- import { createServer } from "node:net";
2379
- import path3 from "node:path";
2380
- var PS_COMMAND = "/bin/ps";
2381
- var FORMAT_FLAG = "-o";
2382
- var START_TIME_FORMAT = "lstart=";
2383
- var PID_FLAG = "-p";
2384
- var PROBE_ENVIRONMENT = { TZ: "UTC", LC_ALL: "C" };
2385
- var PROBE_TIMEOUT_MS = 2e3;
2386
- var LIVENESS_SIGNAL = 0;
2387
- var LOWEST_REAL_PID = 1;
2388
- var PROC_START_FIELD_INDEX = 19;
2389
- var BEACON_SCHEME = "socket:";
2390
- var BEACON_SUFFIX = ".sock";
2391
- var BEACON_PREFIX = `josh-process-identity-${String(process.getuid?.() ?? "")}-`;
2392
- var BEACON_ID_LENGTH = 36;
2393
- var BEACON_ID_PATTERN = /^[\da-f-]+$/u;
2394
- var WINDOWS_PLATFORM = "win32";
2395
- var SOCKET_PROBE_SOURCE = "const net=require('node:net');const socket=net.createConnection(process.argv[1]);socket.once('connect',()=>process.exit(0));socket.once('error',()=>process.exit(1));socket.setTimeout(1000,()=>process.exit(1));";
2396
- var beacons = /* @__PURE__ */ new Map();
2397
- var exit_cleanup = { is_registered: false };
2398
- function is_live_pid(pid) {
2399
- if (pid < LOWEST_REAL_PID) return false;
2400
- try {
2401
- process.kill(pid, LIVENESS_SIGNAL);
2402
- return true;
2403
- } catch {
2404
- return false;
2405
- }
2406
- }
2407
- function read_proc_start(pid) {
2408
- try {
2409
- const stat = readFileSync4(`/proc/${String(pid)}/stat`, "utf8");
2410
- const fields = stat.slice(stat.lastIndexOf(")") + 1).trim().split(/\s+/u);
2411
- const start = fields[PROC_START_FIELD_INDEX];
2412
- return start === void 0 ? void 0 : `proc:${start}`;
2413
- } catch {
2414
- return void 0;
2415
- }
2416
- }
2417
- function read_ps_start(pid) {
2418
- try {
2419
- const printed = execFileSync2(
2420
- PS_COMMAND,
2421
- [FORMAT_FLAG, START_TIME_FORMAT, PID_FLAG, String(pid)],
2422
- {
2423
- encoding: "utf8",
2424
- timeout: PROBE_TIMEOUT_MS,
2425
- stdio: ["ignore", "pipe", "ignore"],
2426
- env: PROBE_ENVIRONMENT
2427
- }
2428
- ).trim();
2429
- return printed.length > 0 ? printed : void 0;
2430
- } catch {
2431
- return void 0;
2432
- }
2433
- }
2434
- var START_PROBES = { read_proc_start, read_ps_start };
2435
- function safe_probe(probe, pid) {
2436
- try {
2437
- return probe(pid);
2438
- } catch {
2439
- return void 0;
2440
- }
2441
- }
2442
- function read_start(pid, probes = START_PROBES) {
2443
- return safe_probe(probes.read_proc_start, pid) ?? safe_probe(probes.read_ps_start, pid);
2444
- }
2445
- function close_beacon(token) {
2446
- const server = beacons.get(token);
2447
- if (server === void 0) return;
2448
- beacons.delete(token);
2449
- server.close();
2450
- rmSync(token.slice(BEACON_SCHEME.length), { force: true });
2451
- }
2452
- function close_beacons() {
2453
- for (const token of beacons.keys()) close_beacon(token);
2454
- }
2455
- function ensure_exit_cleanup() {
2456
- if (exit_cleanup.is_registered) return;
2457
- exit_cleanup.is_registered = true;
2458
- process.once("exit", close_beacons);
2459
- }
2460
- function ignore_beacon_error() {
2461
- }
2462
- function bind_beacon(target) {
2463
- return createServer().on("error", ignore_beacon_error).listen(target);
2464
- }
2465
- function try_bind_beacon(bind, target) {
2466
- try {
2467
- const server = bind(target);
2468
- return server.listening ? server : void 0;
2469
- } catch {
2470
- return void 0;
2471
- }
2472
- }
2473
- function open_beacon(bind = bind_beacon) {
2474
- const target = path3.join(PLATFORM_TEMP_ROOT, `${BEACON_PREFIX}${randomUUID()}${BEACON_SUFFIX}`);
2475
- const token = `${BEACON_SCHEME}${target}`;
2476
- const server = try_bind_beacon(bind, target);
2477
- if (server === void 0) return void 0;
2478
- server.unref();
2479
- beacons.set(token, server);
2480
- ensure_exit_cleanup();
2481
- return token;
2482
- }
2483
- function resolve_own_start(probes = START_PROBES, platform2 = process.platform) {
2484
- const start = read_start(process.pid, probes);
2485
- if (start !== void 0 || platform2 === WINDOWS_PLATFORM) return start;
2486
- return open_beacon();
2487
- }
2488
- function is_valid_beacon_name(name) {
2489
- if (!name.startsWith(BEACON_PREFIX) || !name.endsWith(BEACON_SUFFIX)) return false;
2490
- const id = name.slice(BEACON_PREFIX.length, -BEACON_SUFFIX.length);
2491
- return id.length === BEACON_ID_LENGTH && BEACON_ID_PATTERN.test(id);
2492
- }
2493
- function beacon_target(token) {
2494
- if (!token.startsWith(BEACON_SCHEME)) return void 0;
2495
- const target = token.slice(BEACON_SCHEME.length);
2496
- if (path3.dirname(target) !== PLATFORM_TEMP_ROOT) return void 0;
2497
- return is_valid_beacon_name(path3.basename(target)) ? target : void 0;
2498
- }
2499
- function is_live_beacon(target) {
2500
- return spawnSync(process.execPath, ["-e", SOCKET_PROBE_SOURCE, target], {
2501
- stdio: "ignore",
2502
- timeout: PROBE_TIMEOUT_MS
2503
- }).status === 0;
2504
- }
2505
- function has_matching_start(pid, recorded_start, read2) {
2506
- const observed = read2(pid);
2507
- return observed === void 0 ? void 0 : observed === recorded_start;
2508
- }
2509
- function matches_recorded_start(pid, recorded_start, read2) {
2510
- const target = beacon_target(recorded_start);
2511
- return target === void 0 ? has_matching_start(pid, recorded_start, read2) : is_live_beacon(target);
2512
- }
2513
- var own_probe = /* @__PURE__ */ new Map();
2514
- function own_start() {
2515
- if (!own_probe.has(process.pid)) own_probe.set(process.pid, resolve_own_start());
2516
- return own_probe.get(process.pid);
2517
- }
2518
- function own_fields() {
2519
- const start = own_start();
2520
- return { pid: process.pid, ...start !== void 0 && { process_start: start } };
2521
- }
2522
- function is_same_process(pid, recorded_start, read2 = read_start) {
2523
- if (pid === void 0 || !is_live_pid(pid)) return false;
2524
- if (recorded_start === void 0) return void 0;
2525
- return matches_recorded_start(pid, recorded_start, read2);
2526
- }
2527
- function is_own_process(pid, recorded_start) {
2528
- return pid === process.pid && recorded_start === own_start();
2529
- }
2530
- var process_identity = {
2531
- close_beacon,
2532
- is_live_pid,
2533
- is_own_process,
2534
- is_same_process,
2535
- open_beacon,
2536
- own_fields,
2537
- own_start,
2538
- read_start,
2539
- resolve_own_start
2540
- };
2541
-
2542
2766
  // scripts/run/run-carry.ts
2543
2767
  import { z as z5 } from "zod";
2544
2768
 
@@ -3300,6 +3524,8 @@ export {
3300
3524
  ALREADY_DONE_LABEL,
3301
3525
  has_any_label,
3302
3526
  has_label_name,
3527
+ run_tail,
3528
+ lane_background,
3303
3529
  lane_park,
3304
3530
  repo_party,
3305
3531
  FULL_PAGE_QUERY,
@@ -3310,8 +3536,8 @@ export {
3310
3536
  gh_failure,
3311
3537
  has_stderr_field,
3312
3538
  git_gh_exec,
3313
- lane_registry,
3314
3539
  process_identity,
3540
+ lane_registry,
3315
3541
  run_invocation,
3316
3542
  run_carry,
3317
3543
  run_watcher_guard