@brainervirus/workit-cursor 8.13.1 → 8.14.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.
@@ -21983,6 +21983,8 @@ var workspaceConfigSchema = object2({
21983
21983
  defaultEndpoint: unknown().optional(),
21984
21984
  verification: _enum(["self", "independent"]).optional(),
21985
21985
  trailers: boolean2().optional(),
21986
+ liveSessionGuard: boolean2().optional(),
21987
+ fanout: object2({ tierModels: unknown().optional() }).passthrough().optional(),
21986
21988
  autonomy: object2(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
21987
21989
  kind,
21988
21990
  union([boolean2(), literal("verified")]).optional()
@@ -22300,6 +22302,17 @@ var resolveRuntimeReleaseTracks = (cwd) => {
22300
22302
  const matched = matchRuntimeWorkspace(cwd);
22301
22303
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
22302
22304
  };
22305
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
22306
+ var TIER_HOSTS = new Set([
22307
+ "claude_code",
22308
+ "opencode",
22309
+ "codex",
22310
+ "codex_cli",
22311
+ "codex_desktop",
22312
+ "cursor",
22313
+ "pi",
22314
+ "default"
22315
+ ]);
22303
22316
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
22304
22317
  const matched = matchRuntimeWorkspace(cwd);
22305
22318
  if (!matched)
@@ -28462,10 +28475,44 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
28462
28475
  var signals = new Map;
28463
28476
  var cache = new Map;
28464
28477
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
28478
+ // packages/workit-core/src/core/live-session.ts
28479
+ import { spawnSync as spawnSync15 } from "node:child_process";
28480
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
28481
+ var liveProbe = {
28482
+ platform: process.platform,
28483
+ run: (args) => spawnSync15("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
28484
+ start: (pid) => processStartOf(pid)
28485
+ };
28486
+ var PIDS = new Map;
28487
+
28465
28488
  // packages/workit-core/src/hooks/write-gate.ts
28466
28489
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
28467
28490
  var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
28468
28491
  var cache2 = new Map;
28492
+ var MUTATING_GIT = new Set([
28493
+ "add",
28494
+ "am",
28495
+ "apply",
28496
+ "checkout",
28497
+ "cherry-pick",
28498
+ "clean",
28499
+ "commit",
28500
+ "merge",
28501
+ "mv",
28502
+ "pull",
28503
+ "rebase",
28504
+ "reset",
28505
+ "restore",
28506
+ "revert",
28507
+ "rm",
28508
+ "stash",
28509
+ "switch"
28510
+ ]);
28511
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
28512
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
28513
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
28514
+ var PUSHD2 = new Set(["pushd", "push-location"]);
28515
+ var POPD2 = new Set(["popd", "pop-location"]);
28469
28516
  // packages/workit-core/src/core/skill-manifests.ts
28470
28517
  if (false) {}
28471
28518
 
@@ -9355,6 +9355,8 @@ var workspaceConfigSchema = object({
9355
9355
  defaultEndpoint: unknown().optional(),
9356
9356
  verification: _enum(["self", "independent"]).optional(),
9357
9357
  trailers: boolean2().optional(),
9358
+ liveSessionGuard: boolean2().optional(),
9359
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
9358
9360
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
9359
9361
  kind,
9360
9362
  union([boolean2(), literal("verified")]).optional()
@@ -9672,24 +9674,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
9672
9674
  const matched = matchRuntimeWorkspace(cwd);
9673
9675
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
9674
9676
  };
9675
- var workspaceTrailers = (cwd, dir) => {
9677
+ var inheritedSetting = (cwd, dir, has) => {
9676
9678
  let matched;
9677
9679
  try {
9678
9680
  matched = matchRuntimeWorkspace(cwd, dir);
9679
9681
  } catch {
9680
- return { enabled: true, workspace: null, from: null };
9682
+ return null;
9681
9683
  }
9682
9684
  if (!matched)
9683
- return { enabled: true, workspace: null, from: null };
9685
+ return null;
9684
9686
  const { selected, matches } = matched;
9685
9687
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
9686
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
9688
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
9689
+ };
9690
+ var workspaceTrailers = (cwd, dir) => {
9691
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
9687
9692
  return {
9688
- enabled: source?.trailers !== false,
9689
- workspace: selected.name,
9690
- from: source?.name ?? null
9693
+ enabled: found?.source?.trailers !== false,
9694
+ workspace: found?.workspace ?? null,
9695
+ from: found?.source?.name ?? null
9691
9696
  };
9692
9697
  };
9698
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
9699
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
9700
+ var TIER_HOSTS = new Set([
9701
+ "claude_code",
9702
+ "opencode",
9703
+ "codex",
9704
+ "codex_cli",
9705
+ "codex_desktop",
9706
+ "cursor",
9707
+ "pi",
9708
+ "default"
9709
+ ]);
9693
9710
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
9694
9711
  const matched = matchRuntimeWorkspace(cwd);
9695
9712
  if (!matched)
@@ -20220,27 +20237,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20220
20237
  };
20221
20238
  // packages/workit-core/src/hooks/write-gate.ts
20222
20239
  import { realpathSync as realpathSync5 } from "node:fs";
20240
+ import os7 from "node:os";
20241
+ import path23 from "node:path";
20242
+
20243
+ // packages/workit-core/src/core/live-session.ts
20244
+ import { spawnSync as spawnSync13 } from "node:child_process";
20245
+ import { createHash as createHash15 } from "node:crypto";
20246
+ import fs17 from "node:fs";
20247
+ import os6 from "node:os";
20223
20248
  import path22 from "node:path";
20249
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
20250
+ var HEARTBEAT_EVERY_MS = 30000;
20251
+ var liveProbe = {
20252
+ platform: process.platform,
20253
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20254
+ start: (pid) => processStartOf(pid)
20255
+ };
20256
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
20257
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
20258
+ var processAlive = (pid) => {
20259
+ try {
20260
+ process.kill(pid, 0);
20261
+ return true;
20262
+ } catch (error) {
20263
+ return error.code === "EPERM";
20264
+ }
20265
+ };
20266
+ var processInfo = (pid) => {
20267
+ try {
20268
+ if (liveProbe.platform === "linux") {
20269
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20270
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20271
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20272
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20273
+ }
20274
+ if (liveProbe.platform === "win32")
20275
+ return null;
20276
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20277
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20278
+ } catch {
20279
+ return null;
20280
+ }
20281
+ };
20282
+ var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
20283
+ var PIDS = new Map;
20284
+ var hostPid = (transport) => {
20285
+ if (transport !== "hook-process")
20286
+ return process.pid;
20287
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20288
+ if (PIDS.has(key))
20289
+ return PIDS.get(key) ?? null;
20290
+ if (liveProbe.platform === "win32") {
20291
+ PIDS.set(key, null);
20292
+ return null;
20293
+ }
20294
+ let pid = process.ppid;
20295
+ for (let hop = 0;hop < 4; hop++) {
20296
+ const info = processInfo(pid);
20297
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20298
+ break;
20299
+ pid = info.ppid;
20300
+ }
20301
+ PIDS.set(key, pid);
20302
+ return pid;
20303
+ };
20304
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
20305
+ var liveOwner = (input, transport, env = process.env) => {
20306
+ const session = input.session.id?.trim();
20307
+ if (!session)
20308
+ return null;
20309
+ const leads = new Set([leadOf(sessionSafe(session))]);
20310
+ if (input.session.parentId)
20311
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20312
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20313
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20314
+ return {
20315
+ host: input.host,
20316
+ session: sessionSafe(session),
20317
+ leads: [...leads],
20318
+ pid: () => hostPid(transport)
20319
+ };
20320
+ };
20321
+ var checkoutOf = (target) => {
20322
+ let dir = path22.resolve(target);
20323
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20324
+ dir = path22.dirname(dir);
20325
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20326
+ dir = path22.dirname(dir);
20327
+ const parts = dir.split(path22.sep);
20328
+ const dotGit = parts.lastIndexOf(".git");
20329
+ if (dotGit > 0)
20330
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20331
+ const location = resolveStore(dir);
20332
+ if (location instanceof Error || !location.shared || !location.top)
20333
+ return null;
20334
+ let top = location.top;
20335
+ try {
20336
+ top = fs17.realpathSync.native(top);
20337
+ } catch {}
20338
+ return top;
20339
+ };
20340
+ var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20341
+ var checkoutKey = (top, platform = process.platform) => {
20342
+ let key = path22.resolve(top);
20343
+ try {
20344
+ key = fs17.realpathSync.native(key);
20345
+ } catch {}
20346
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20347
+ };
20348
+ var readMarker = (file) => {
20349
+ try {
20350
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20351
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20352
+ return null;
20353
+ return {
20354
+ version: 1,
20355
+ session: value.session,
20356
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20357
+ host: value.host,
20358
+ pid: typeof value.pid === "number" ? value.pid : null,
20359
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20360
+ machine: typeof value.machine === "string" ? value.machine : "",
20361
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20362
+ heartbeatAt: value.heartbeatAt
20363
+ };
20364
+ } catch {
20365
+ return null;
20366
+ }
20367
+ };
20368
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
20369
+ var isLive = (marker, deps) => {
20370
+ const age = ageOf2(marker, deps.now ?? new Date);
20371
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20372
+ return false;
20373
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20374
+ return true;
20375
+ if (!(deps.alive ?? processAlive)(marker.pid))
20376
+ return false;
20377
+ if (marker.pidStart === null)
20378
+ return true;
20379
+ const start = liveProbe.start(marker.pid);
20380
+ return start === null || start === marker.pidStart;
20381
+ };
20382
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
20383
+ var sameOwner = (marker, owner, machine) => {
20384
+ if (sameLead(marker, owner))
20385
+ return true;
20386
+ if (marker.machine !== machine)
20387
+ return false;
20388
+ const pid = owner.pid();
20389
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20390
+ return familyOf(marker.host) === familyOf(owner.host);
20391
+ return marker.pid === pid;
20392
+ };
20393
+ var liveConflict = (target, owner, deps = {}) => {
20394
+ try {
20395
+ const checkout = checkoutOf(target);
20396
+ if (!checkout)
20397
+ return null;
20398
+ const file = markerFile2(checkout);
20399
+ const marker = readMarker(file);
20400
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20401
+ return null;
20402
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20403
+ return null;
20404
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20405
+ } catch {
20406
+ return null;
20407
+ }
20408
+ };
20409
+ var claimLiveSession = (target, owner, deps = {}) => {
20410
+ try {
20411
+ const checkout = checkoutOf(target);
20412
+ if (!checkout)
20413
+ return;
20414
+ const file = markerFile2(checkout);
20415
+ const now = deps.now ?? new Date;
20416
+ const machine = deps.machine ?? os6.hostname();
20417
+ const current = readMarker(file);
20418
+ if (current) {
20419
+ const age = ageOf2(current, now);
20420
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20421
+ return;
20422
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20423
+ return;
20424
+ }
20425
+ const pid = owner.pid();
20426
+ const marker = {
20427
+ version: 1,
20428
+ session: owner.session,
20429
+ leads: owner.leads,
20430
+ host: owner.host,
20431
+ pid,
20432
+ pidStart: pid === null ? null : liveProbe.start(pid),
20433
+ machine,
20434
+ checkout,
20435
+ heartbeatAt: now.toISOString()
20436
+ };
20437
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20438
+ const temp = `${file}.${process.pid}.tmp`;
20439
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20440
+ fs17.renameSync(temp, file);
20441
+ } catch {}
20442
+ };
20443
+
20444
+ // packages/workit-core/src/hooks/write-gate.ts
20224
20445
  var NONE3 = { kind: "none" };
20225
20446
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20226
20447
  var canonical2 = (file) => {
20227
20448
  let rest = "";
20228
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20449
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20229
20450
  try {
20230
- return path22.join(realpathSync5(dir), rest);
20451
+ return path23.join(realpathSync5(dir), rest);
20231
20452
  } catch {
20232
- if (path22.dirname(dir) === dir)
20233
- return path22.resolve(file);
20234
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20453
+ if (path23.dirname(dir) === dir)
20454
+ return path23.resolve(file);
20455
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20235
20456
  }
20236
20457
  }
20237
20458
  };
20238
20459
  var exempt = (cwd, root, file, plans) => {
20239
20460
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20240
- const relative = path22.relative(fold(canonical2(root)), fold(canonical2(path22.resolve(cwd, file))));
20241
- if (relative.startsWith("..") || path22.isAbsolute(relative))
20461
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20462
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20242
20463
  return true;
20243
- const posix = relative.split(path22.sep).join("/");
20464
+ const posix = relative.split(path23.sep).join("/");
20244
20465
  const segments = posix.split("/");
20245
20466
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20246
20467
  };
@@ -20338,7 +20559,7 @@ var shellWrites = (command) => {
20338
20559
  return { writes: true, targets: unknown ? null : targets };
20339
20560
  };
20340
20561
  var cache2 = new Map;
20341
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00");
20562
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
20342
20563
  var gateFor = (cwd) => {
20343
20564
  const store = new TaskStore(cwd);
20344
20565
  const listed = store.listTaskIndex();
@@ -20376,7 +20597,218 @@ var gateFor = (cwd) => {
20376
20597
  cache2.delete(cache2.keys().next().value);
20377
20598
  return gate;
20378
20599
  };
20379
- function writeGate(cwd, paths) {
20600
+ var MUTATING_GIT = new Set([
20601
+ "add",
20602
+ "am",
20603
+ "apply",
20604
+ "checkout",
20605
+ "cherry-pick",
20606
+ "clean",
20607
+ "commit",
20608
+ "merge",
20609
+ "mv",
20610
+ "pull",
20611
+ "rebase",
20612
+ "reset",
20613
+ "restore",
20614
+ "revert",
20615
+ "rm",
20616
+ "stash",
20617
+ "switch"
20618
+ ]);
20619
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
20620
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20621
+ var DIR_TARGET = ".workit-live-target";
20622
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
20623
+ var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
20624
+ var gitMutationDirs = (cwd, command, dialect) => {
20625
+ let invocations;
20626
+ try {
20627
+ invocations = rawInvocations(command, dialect);
20628
+ } catch {
20629
+ return [];
20630
+ }
20631
+ return invocations.flatMap((invocation) => {
20632
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20633
+ return [];
20634
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20635
+ const sub = invocation.args[at] ?? "";
20636
+ const rest = invocation.args.slice(at + 1);
20637
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20638
+ return [];
20639
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20640
+ return [];
20641
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20642
+ });
20643
+ };
20644
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20645
+ var PUSHD2 = new Set(["pushd", "push-location"]);
20646
+ var POPD2 = new Set(["popd", "pop-location"]);
20647
+ var cdInto = (dir, words) => {
20648
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20649
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20650
+ return;
20651
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20652
+ };
20653
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20654
+ var segmentTargets = (cwd, command, dialect) => {
20655
+ const targets = [];
20656
+ let dir = cwd;
20657
+ let stack = [];
20658
+ const groups = [];
20659
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20660
+ for (const segment of segmentsOf(command, dialect)) {
20661
+ if (segment.group === "open") {
20662
+ groups.push({ dir, stack: [...stack] });
20663
+ continue;
20664
+ }
20665
+ if (segment.group === "close") {
20666
+ const saved = groups.pop();
20667
+ if (saved)
20668
+ ({ dir, stack } = saved);
20669
+ continue;
20670
+ }
20671
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20672
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20673
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20674
+ if (CD2.has(name)) {
20675
+ dir = cdInto(dir, words);
20676
+ continue;
20677
+ }
20678
+ if (PUSHD2.has(name)) {
20679
+ stack.push(dir);
20680
+ dir = cdInto(dir, words);
20681
+ continue;
20682
+ }
20683
+ if (POPD2.has(name)) {
20684
+ dir = stack.length ? stack.pop() : undefined;
20685
+ continue;
20686
+ }
20687
+ for (const file of segment.redirects)
20688
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20689
+ const resolved = at(file);
20690
+ if (resolved)
20691
+ targets.push(resolved);
20692
+ }
20693
+ const edits = editsOf(segment.words);
20694
+ if (edits === null) {
20695
+ if (dir !== undefined)
20696
+ targets.push(path23.join(dir, DIR_TARGET));
20697
+ } else
20698
+ for (const file of edits) {
20699
+ const resolved = at(file);
20700
+ if (resolved)
20701
+ targets.push(resolved);
20702
+ }
20703
+ if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
20704
+ targets.push(path23.join(dir, DIR_TARGET));
20705
+ }
20706
+ return targets;
20707
+ };
20708
+ var overridden = (command, dialect) => {
20709
+ const segments = segmentsOf(command, dialect);
20710
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20711
+ return false;
20712
+ const words = segments[0].words;
20713
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20714
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20715
+ };
20716
+ var HOST_LABELS = {
20717
+ claude_code: "Claude Code",
20718
+ opencode: "OpenCode",
20719
+ codex_cli: "Codex CLI",
20720
+ codex_desktop: "Codex Desktop",
20721
+ cursor: "Cursor",
20722
+ pi: "Pi"
20723
+ };
20724
+ var ageText = (ms) => {
20725
+ const seconds = Math.max(0, Math.round(ms / 1000));
20726
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20727
+ };
20728
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20729
+ const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
20730
+ return {
20731
+ kind: "deny",
20732
+ reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
20733
+ unblock
20734
+ };
20735
+ };
20736
+ var guarded = (targets) => {
20737
+ const seen = new Set;
20738
+ return targets.filter((target) => {
20739
+ const dir = path23.dirname(target);
20740
+ if (seen.has(dir))
20741
+ return false;
20742
+ seen.add(dir);
20743
+ return true;
20744
+ });
20745
+ };
20746
+ var liveGate = (owner, targets, deps) => {
20747
+ try {
20748
+ for (const target of guarded(targets)) {
20749
+ const conflict = liveConflict(target, owner, deps);
20750
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20751
+ return liveDeny(conflict);
20752
+ }
20753
+ } catch {}
20754
+ return NONE3;
20755
+ };
20756
+ var claimAll = (owner, targets, deps) => {
20757
+ for (const target of guarded(targets))
20758
+ claimLiveSession(target, owner, deps);
20759
+ };
20760
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
20761
+ function writeGate(cwd, paths, owner, deps) {
20762
+ if (!owner)
20763
+ return beforeWriteGate(cwd, paths);
20764
+ const targets = liveTargets(cwd, paths);
20765
+ const live = liveGate(owner, targets, deps);
20766
+ if (live.kind !== "none")
20767
+ return live;
20768
+ const decision = beforeWriteGate(cwd, paths);
20769
+ if (decision.kind === "none")
20770
+ claimAll(owner, targets, deps);
20771
+ return decision;
20772
+ }
20773
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20774
+ const writes = shellWrites(command);
20775
+ let targets = [];
20776
+ if (owner) {
20777
+ try {
20778
+ targets = [
20779
+ ...segmentTargets(cwd, command, dialect),
20780
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20781
+ ];
20782
+ } catch {
20783
+ targets = [];
20784
+ }
20785
+ }
20786
+ if (owner && targets.length) {
20787
+ const live = liveGate(owner, targets, deps);
20788
+ if (live.kind !== "none") {
20789
+ if (!overridden(command, dialect))
20790
+ return live;
20791
+ noteOverride(cwd, owner, command);
20792
+ }
20793
+ }
20794
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20795
+ if (owner && targets.length && decision.kind === "none")
20796
+ claimAll(owner, targets, deps);
20797
+ return decision;
20798
+ }
20799
+ var noteOverride = (cwd, owner, command) => {
20800
+ try {
20801
+ if (!inWorkitWorkspace(cwd))
20802
+ return;
20803
+ appendHookObserved(cwd, {
20804
+ type: "live_session.override",
20805
+ actor: { host: owner.host, session: owner.session, agentId: null },
20806
+ session: owner.session,
20807
+ command: command.slice(0, 200)
20808
+ });
20809
+ } catch {}
20810
+ };
20811
+ function beforeWriteGate(cwd, paths) {
20380
20812
  try {
20381
20813
  const gate = gateFor(cwd);
20382
20814
  if (!gate || gate.blockers.length === 0)
@@ -20394,7 +20826,7 @@ function writeGate(cwd, paths) {
20394
20826
  }
20395
20827
  }
20396
20828
  // packages/workit-core/src/hooks/stop.ts
20397
- import { spawnSync as spawnSync13 } from "node:child_process";
20829
+ import { spawnSync as spawnSync14 } from "node:child_process";
20398
20830
  var NONE4 = { kind: "none" };
20399
20831
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20400
20832
  var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
@@ -20415,7 +20847,7 @@ var asksUser = (message) => {
20415
20847
  };
20416
20848
  var short = (sha) => (sha ?? "?").slice(0, 12);
20417
20849
  var gitList = (cwd, args) => {
20418
- const run = spawnSync13("git", args, {
20850
+ const run = spawnSync14("git", args, {
20419
20851
  cwd,
20420
20852
  encoding: "utf8",
20421
20853
  timeout: 3000,
@@ -20779,12 +21211,12 @@ ${line}` };
20779
21211
  };
20780
21212
  // packages/workit-core/src/hooks/hosts/fields.ts
20781
21213
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20782
- import path23 from "node:path";
21214
+ import path24 from "node:path";
20783
21215
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20784
21216
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20785
21217
  var optionalText = (value) => typeof value === "string" ? value : null;
20786
21218
  var existingDirectory = (value) => {
20787
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21219
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20788
21220
  return null;
20789
21221
  try {
20790
21222
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20929,8 +21361,7 @@ function handleHook(input, deps) {
20929
21361
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20930
21362
  if (raw.kind === "deny")
20931
21363
  return raw;
20932
- const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
20933
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
21364
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20934
21365
  if (gate.kind === "deny")
20935
21366
  return gate;
20936
21367
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20943,7 +21374,7 @@ function handleHook(input, deps) {
20943
21374
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20944
21375
  return NONE5;
20945
21376
  case "write.pre":
20946
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21377
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20947
21378
  case "subagent.start":
20948
21379
  return {
20949
21380
  kind: "context",
@@ -21522,8 +21953,8 @@ var codexAdapter = {
21522
21953
  };
21523
21954
 
21524
21955
  // packages/workit-core/src/hooks/hosts/cursor.ts
21525
- import fs17 from "node:fs";
21526
- import path24 from "node:path";
21956
+ import fs18 from "node:fs";
21957
+ import path25 from "node:path";
21527
21958
  var undocumented = { support: "undocumented", native: null };
21528
21959
  var none = { support: "none", native: null };
21529
21960
  var CURSOR_DESCRIPTOR = {
@@ -21720,17 +22151,17 @@ var protocolEvent2 = (input) => {
21720
22151
  };
21721
22152
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21722
22153
  var lastAssistantText = (file) => {
21723
- if (!file || !path24.isAbsolute(file))
22154
+ if (!file || !path25.isAbsolute(file))
21724
22155
  return null;
21725
22156
  try {
21726
- const size = fs17.statSync(file).size;
22157
+ const size = fs18.statSync(file).size;
21727
22158
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21728
22159
  const buffer = Buffer.alloc(size - start);
21729
- const fd = fs17.openSync(file, "r");
22160
+ const fd = fs18.openSync(file, "r");
21730
22161
  try {
21731
- fs17.readSync(fd, buffer, 0, buffer.length, start);
22162
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21732
22163
  } finally {
21733
- fs17.closeSync(fd);
22164
+ fs18.closeSync(fd);
21734
22165
  }
21735
22166
  const lines = buffer.toString("utf8").split(`
21736
22167
  `);
@@ -21757,7 +22188,7 @@ var lastAssistantText = (file) => {
21757
22188
  return null;
21758
22189
  }
21759
22190
  };
21760
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`);
22191
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
21761
22192
  var hookCwd = (input) => {
21762
22193
  const cwd = existingDirectory(input.cwd);
21763
22194
  if (cwd && input.hook_event_name === "beforeShellExecution")