@brainervirus/workit-cursor 8.13.1 → 8.14.1

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.
@@ -7548,6 +7548,8 @@ var text = string2().check((ctx) => {
7548
7548
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7549
7549
  });
7550
7550
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7551
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7552
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7551
7553
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7552
7554
  var revision = text.regex(uuidPattern, "expected UUID revision");
7553
7555
  var validUtc = (value) => {
@@ -7925,7 +7927,7 @@ var workerReportSchema = object({
7925
7927
  outcome: _enum(["completed", "failed", "cancelled"]),
7926
7928
  summary: text,
7927
7929
  evidenceIds: array(id),
7928
- findingIds: array(id)
7930
+ findingIds: array(idRef)
7929
7931
  }).strict();
7930
7932
  var workerSchema = object({
7931
7933
  assignment: assignmentSchema,
@@ -8171,7 +8173,7 @@ var findingOperations = {
8171
8173
  action: literal("resolve"),
8172
8174
  ...taskId,
8173
8175
  expectedRevision: revision.optional(),
8174
- findingId: id,
8176
+ findingId: idRef,
8175
8177
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8176
8178
  reason: text,
8177
8179
  evidenceIds: array(id),
@@ -8212,7 +8214,7 @@ var workerOperations = {
8212
8214
  ...taskId,
8213
8215
  expectedRevision: revision.optional(),
8214
8216
  expectedWorkspaceRevision: revision.optional(),
8215
- workerId: id,
8217
+ workerId: idRef,
8216
8218
  report: workerReportSchema
8217
8219
  }),
8218
8220
  cancel: operation({
@@ -8220,7 +8222,7 @@ var workerOperations = {
8220
8222
  ...taskId,
8221
8223
  ...revisions,
8222
8224
  expectedWorkspaceRevision: revision.optional(),
8223
- workerId: id,
8225
+ workerId: idRef,
8224
8226
  reason: text
8225
8227
  })
8226
8228
  };
@@ -8270,6 +8272,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8270
8272
  data
8271
8273
  });
8272
8274
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8275
+ function resolveIdRef(kind, ref, ids) {
8276
+ if (ids.includes(ref))
8277
+ return success(null, null, ref);
8278
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8279
+ if (matches.length === 1)
8280
+ return success(null, null, matches[0]);
8281
+ if (matches.length === 0)
8282
+ return failure2("not_found", `${kind} not found`);
8283
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8284
+ }
8273
8285
  function parseOperation(family, input) {
8274
8286
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8275
8287
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -9355,6 +9367,8 @@ var workspaceConfigSchema = object({
9355
9367
  defaultEndpoint: unknown().optional(),
9356
9368
  verification: _enum(["self", "independent"]).optional(),
9357
9369
  trailers: boolean2().optional(),
9370
+ liveSessionGuard: boolean2().optional(),
9371
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
9358
9372
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
9359
9373
  kind,
9360
9374
  union([boolean2(), literal("verified")]).optional()
@@ -9672,24 +9686,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
9672
9686
  const matched = matchRuntimeWorkspace(cwd);
9673
9687
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
9674
9688
  };
9675
- var workspaceTrailers = (cwd, dir) => {
9689
+ var inheritedSetting = (cwd, dir, has) => {
9676
9690
  let matched;
9677
9691
  try {
9678
9692
  matched = matchRuntimeWorkspace(cwd, dir);
9679
9693
  } catch {
9680
- return { enabled: true, workspace: null, from: null };
9694
+ return null;
9681
9695
  }
9682
9696
  if (!matched)
9683
- return { enabled: true, workspace: null, from: null };
9697
+ return null;
9684
9698
  const { selected, matches } = matched;
9685
9699
  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");
9700
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
9701
+ };
9702
+ var workspaceTrailers = (cwd, dir) => {
9703
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
9687
9704
  return {
9688
- enabled: source?.trailers !== false,
9689
- workspace: selected.name,
9690
- from: source?.name ?? null
9705
+ enabled: found?.source?.trailers !== false,
9706
+ workspace: found?.workspace ?? null,
9707
+ from: found?.source?.name ?? null
9691
9708
  };
9692
9709
  };
9710
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
9711
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
9712
+ var TIER_HOSTS = new Set([
9713
+ "claude_code",
9714
+ "opencode",
9715
+ "codex",
9716
+ "codex_cli",
9717
+ "codex_desktop",
9718
+ "cursor",
9719
+ "pi",
9720
+ "default"
9721
+ ]);
9693
9722
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
9694
9723
  const matched = matchRuntimeWorkspace(cwd);
9695
9724
  if (!matched)
@@ -13614,20 +13643,26 @@ class TaskStore {
13614
13643
  return success(null, null, { key: key.data, bound: bound.data });
13615
13644
  }
13616
13645
  taskForWorker(workerId) {
13617
- if (typeof workerId !== "string" || !validId(workerId))
13646
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
13618
13647
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
13619
13648
  const listed = this.listTaskIndex();
13620
13649
  if (!listed.ok)
13621
13650
  return listed;
13651
+ const owners = new Map;
13622
13652
  for (const entry of listed.data) {
13623
13653
  if (entry.status === "closed")
13624
13654
  continue;
13625
13655
  const loaded = this.load(entry.id);
13626
13656
  if (!loaded.ok)
13627
13657
  return loaded;
13628
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
13629
- return success(null, null, entry.id);
13658
+ for (const worker of loaded.data?.record.workers ?? [])
13659
+ owners.set(worker.id, entry.id);
13630
13660
  }
13661
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
13662
+ if (resolved.ok)
13663
+ return success(null, null, owners.get(resolved.data) ?? null);
13664
+ if (resolved.code !== "not_found")
13665
+ return resolved;
13631
13666
  return success(null, null, null);
13632
13667
  }
13633
13668
  originTaskEntry() {
@@ -16253,6 +16288,8 @@ var TASK_SCOPED_ACTIONS = new Set([
16253
16288
  "worker.cancel",
16254
16289
  "state.export"
16255
16290
  ]);
16291
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
16292
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
16256
16293
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
16257
16294
  var workerCommand = (taskId, request) => {
16258
16295
  const { action, workerId, reason, report } = request;
@@ -17150,6 +17187,10 @@ class WorkitCore {
17150
17187
  }
17151
17188
  if (helper?.ok)
17152
17189
  return failure2("permission_denied", "helpers cannot resolve findings");
17190
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
17191
+ if (!findingRef.ok)
17192
+ return findingRef;
17193
+ input.findingId = findingRef.data;
17153
17194
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
17154
17195
  if (!finding)
17155
17196
  return failure2("not_found", "finding not found");
@@ -17301,15 +17342,31 @@ class WorkitCore {
17301
17342
  return changed;
17302
17343
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
17303
17344
  }
17345
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
17346
+ if (!workerRef.ok)
17347
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
17348
+ input.workerId = workerRef.data;
17304
17349
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
17305
17350
  if (!entry)
17306
17351
  return failure2("not_found", "worker not found");
17307
17352
  if (input.action === "report") {
17308
17353
  if (helperId === null || helperId !== input.workerId)
17309
- return failure2("permission_denied", "workers can submit only their own report");
17354
+ return failure2("permission_denied", ONLY_OWN_REPORT);
17310
17355
  const helper = this.helperEntry(task.data, true, true);
17311
17356
  if (!helper.ok)
17312
17357
  return helper;
17358
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
17359
+ const reported = [];
17360
+ for (const findingRef of input.report.findingIds) {
17361
+ const found = resolveIdRef("finding", findingRef, ownFindings);
17362
+ if (found.ok)
17363
+ reported.push(found.data);
17364
+ else if (found.code === "not_found")
17365
+ reported.push(findingRef);
17366
+ else
17367
+ return found;
17368
+ }
17369
+ input.report.findingIds = reported;
17313
17370
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
17314
17371
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
17315
17372
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -17337,7 +17394,7 @@ class WorkitCore {
17337
17394
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
17338
17395
  }
17339
17396
  if (helperId !== null)
17340
- return failure2("permission_denied", "helpers cannot cancel or control workers");
17397
+ return failure2("permission_denied", NO_HELPER_CONTROL);
17341
17398
  if (task.data.status === "closed")
17342
17399
  return failure2("invalid_transition", "closed task cannot cancel workers");
17343
17400
  if (entry.data.state === "stopped") {
@@ -20220,27 +20277,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20220
20277
  };
20221
20278
  // packages/workit-core/src/hooks/write-gate.ts
20222
20279
  import { realpathSync as realpathSync5 } from "node:fs";
20280
+ import os7 from "node:os";
20281
+ import path23 from "node:path";
20282
+
20283
+ // packages/workit-core/src/core/live-session.ts
20284
+ import { spawnSync as spawnSync13 } from "node:child_process";
20285
+ import { createHash as createHash15 } from "node:crypto";
20286
+ import fs17 from "node:fs";
20287
+ import os6 from "node:os";
20223
20288
  import path22 from "node:path";
20289
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
20290
+ var HEARTBEAT_EVERY_MS = 30000;
20291
+ var liveProbe = {
20292
+ platform: process.platform,
20293
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20294
+ start: (pid) => processStartOf(pid)
20295
+ };
20296
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
20297
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
20298
+ var processAlive = (pid) => {
20299
+ try {
20300
+ process.kill(pid, 0);
20301
+ return true;
20302
+ } catch (error) {
20303
+ return error.code === "EPERM";
20304
+ }
20305
+ };
20306
+ var processInfo = (pid) => {
20307
+ try {
20308
+ if (liveProbe.platform === "linux") {
20309
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20310
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20311
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20312
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20313
+ }
20314
+ if (liveProbe.platform === "win32")
20315
+ return null;
20316
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20317
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20318
+ } catch {
20319
+ return null;
20320
+ }
20321
+ };
20322
+ 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;
20323
+ var PIDS = new Map;
20324
+ var hostPid = (transport) => {
20325
+ if (transport !== "hook-process")
20326
+ return process.pid;
20327
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20328
+ if (PIDS.has(key))
20329
+ return PIDS.get(key) ?? null;
20330
+ if (liveProbe.platform === "win32") {
20331
+ PIDS.set(key, null);
20332
+ return null;
20333
+ }
20334
+ let pid = process.ppid;
20335
+ for (let hop = 0;hop < 4; hop++) {
20336
+ const info = processInfo(pid);
20337
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20338
+ break;
20339
+ pid = info.ppid;
20340
+ }
20341
+ PIDS.set(key, pid);
20342
+ return pid;
20343
+ };
20344
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
20345
+ var liveOwner = (input, transport, env = process.env) => {
20346
+ const session = input.session.id?.trim();
20347
+ if (!session)
20348
+ return null;
20349
+ const leads = new Set([leadOf(sessionSafe(session))]);
20350
+ if (input.session.parentId)
20351
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20352
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20353
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20354
+ return {
20355
+ host: input.host,
20356
+ session: sessionSafe(session),
20357
+ leads: [...leads],
20358
+ pid: () => hostPid(transport)
20359
+ };
20360
+ };
20361
+ var checkoutOf = (target) => {
20362
+ let dir = path22.resolve(target);
20363
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20364
+ dir = path22.dirname(dir);
20365
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20366
+ dir = path22.dirname(dir);
20367
+ const parts = dir.split(path22.sep);
20368
+ const dotGit = parts.lastIndexOf(".git");
20369
+ if (dotGit > 0)
20370
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20371
+ const location = resolveStore(dir);
20372
+ if (location instanceof Error || !location.shared || !location.top)
20373
+ return null;
20374
+ let top = location.top;
20375
+ try {
20376
+ top = fs17.realpathSync.native(top);
20377
+ } catch {}
20378
+ return top;
20379
+ };
20380
+ var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20381
+ var checkoutKey = (top, platform = process.platform) => {
20382
+ let key = path22.resolve(top);
20383
+ try {
20384
+ key = fs17.realpathSync.native(key);
20385
+ } catch {}
20386
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20387
+ };
20388
+ var readMarker = (file) => {
20389
+ try {
20390
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20391
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20392
+ return null;
20393
+ return {
20394
+ version: 1,
20395
+ session: value.session,
20396
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20397
+ host: value.host,
20398
+ pid: typeof value.pid === "number" ? value.pid : null,
20399
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20400
+ machine: typeof value.machine === "string" ? value.machine : "",
20401
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20402
+ heartbeatAt: value.heartbeatAt
20403
+ };
20404
+ } catch {
20405
+ return null;
20406
+ }
20407
+ };
20408
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
20409
+ var isLive = (marker, deps) => {
20410
+ const age = ageOf2(marker, deps.now ?? new Date);
20411
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20412
+ return false;
20413
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20414
+ return true;
20415
+ if (!(deps.alive ?? processAlive)(marker.pid))
20416
+ return false;
20417
+ if (marker.pidStart === null)
20418
+ return true;
20419
+ const start = liveProbe.start(marker.pid);
20420
+ return start === null || start === marker.pidStart;
20421
+ };
20422
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
20423
+ var sameOwner = (marker, owner, machine) => {
20424
+ if (sameLead(marker, owner))
20425
+ return true;
20426
+ if (marker.machine !== machine)
20427
+ return false;
20428
+ const pid = owner.pid();
20429
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20430
+ return familyOf(marker.host) === familyOf(owner.host);
20431
+ return marker.pid === pid;
20432
+ };
20433
+ var liveConflict = (target, owner, deps = {}) => {
20434
+ try {
20435
+ const checkout = checkoutOf(target);
20436
+ if (!checkout)
20437
+ return null;
20438
+ const file = markerFile2(checkout);
20439
+ const marker = readMarker(file);
20440
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20441
+ return null;
20442
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20443
+ return null;
20444
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20445
+ } catch {
20446
+ return null;
20447
+ }
20448
+ };
20449
+ var claimLiveSession = (target, owner, deps = {}) => {
20450
+ try {
20451
+ const checkout = checkoutOf(target);
20452
+ if (!checkout)
20453
+ return;
20454
+ const file = markerFile2(checkout);
20455
+ const now = deps.now ?? new Date;
20456
+ const machine = deps.machine ?? os6.hostname();
20457
+ const current = readMarker(file);
20458
+ if (current) {
20459
+ const age = ageOf2(current, now);
20460
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20461
+ return;
20462
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20463
+ return;
20464
+ }
20465
+ const pid = owner.pid();
20466
+ const marker = {
20467
+ version: 1,
20468
+ session: owner.session,
20469
+ leads: owner.leads,
20470
+ host: owner.host,
20471
+ pid,
20472
+ pidStart: pid === null ? null : liveProbe.start(pid),
20473
+ machine,
20474
+ checkout,
20475
+ heartbeatAt: now.toISOString()
20476
+ };
20477
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20478
+ const temp = `${file}.${process.pid}.tmp`;
20479
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20480
+ fs17.renameSync(temp, file);
20481
+ } catch {}
20482
+ };
20483
+
20484
+ // packages/workit-core/src/hooks/write-gate.ts
20224
20485
  var NONE3 = { kind: "none" };
20225
20486
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20226
20487
  var canonical2 = (file) => {
20227
20488
  let rest = "";
20228
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20489
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20229
20490
  try {
20230
- return path22.join(realpathSync5(dir), rest);
20491
+ return path23.join(realpathSync5(dir), rest);
20231
20492
  } catch {
20232
- if (path22.dirname(dir) === dir)
20233
- return path22.resolve(file);
20234
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20493
+ if (path23.dirname(dir) === dir)
20494
+ return path23.resolve(file);
20495
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20235
20496
  }
20236
20497
  }
20237
20498
  };
20238
20499
  var exempt = (cwd, root, file, plans) => {
20239
20500
  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))
20501
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20502
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20242
20503
  return true;
20243
- const posix = relative.split(path22.sep).join("/");
20504
+ const posix = relative.split(path23.sep).join("/");
20244
20505
  const segments = posix.split("/");
20245
20506
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20246
20507
  };
@@ -20338,7 +20599,7 @@ var shellWrites = (command) => {
20338
20599
  return { writes: true, targets: unknown ? null : targets };
20339
20600
  };
20340
20601
  var cache2 = new Map;
20341
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00");
20602
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
20342
20603
  var gateFor = (cwd) => {
20343
20604
  const store = new TaskStore(cwd);
20344
20605
  const listed = store.listTaskIndex();
@@ -20376,7 +20637,218 @@ var gateFor = (cwd) => {
20376
20637
  cache2.delete(cache2.keys().next().value);
20377
20638
  return gate;
20378
20639
  };
20379
- function writeGate(cwd, paths) {
20640
+ var MUTATING_GIT = new Set([
20641
+ "add",
20642
+ "am",
20643
+ "apply",
20644
+ "checkout",
20645
+ "cherry-pick",
20646
+ "clean",
20647
+ "commit",
20648
+ "merge",
20649
+ "mv",
20650
+ "pull",
20651
+ "rebase",
20652
+ "reset",
20653
+ "restore",
20654
+ "revert",
20655
+ "rm",
20656
+ "stash",
20657
+ "switch"
20658
+ ]);
20659
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
20660
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20661
+ var DIR_TARGET = ".workit-live-target";
20662
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
20663
+ 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("--"));
20664
+ var gitMutationDirs = (cwd, command, dialect) => {
20665
+ let invocations;
20666
+ try {
20667
+ invocations = rawInvocations(command, dialect);
20668
+ } catch {
20669
+ return [];
20670
+ }
20671
+ return invocations.flatMap((invocation) => {
20672
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20673
+ return [];
20674
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20675
+ const sub = invocation.args[at] ?? "";
20676
+ const rest = invocation.args.slice(at + 1);
20677
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20678
+ return [];
20679
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20680
+ return [];
20681
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20682
+ });
20683
+ };
20684
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20685
+ var PUSHD2 = new Set(["pushd", "push-location"]);
20686
+ var POPD2 = new Set(["popd", "pop-location"]);
20687
+ var cdInto = (dir, words) => {
20688
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20689
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20690
+ return;
20691
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20692
+ };
20693
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20694
+ var segmentTargets = (cwd, command, dialect) => {
20695
+ const targets = [];
20696
+ let dir = cwd;
20697
+ let stack = [];
20698
+ const groups = [];
20699
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20700
+ for (const segment of segmentsOf(command, dialect)) {
20701
+ if (segment.group === "open") {
20702
+ groups.push({ dir, stack: [...stack] });
20703
+ continue;
20704
+ }
20705
+ if (segment.group === "close") {
20706
+ const saved = groups.pop();
20707
+ if (saved)
20708
+ ({ dir, stack } = saved);
20709
+ continue;
20710
+ }
20711
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20712
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20713
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20714
+ if (CD2.has(name)) {
20715
+ dir = cdInto(dir, words);
20716
+ continue;
20717
+ }
20718
+ if (PUSHD2.has(name)) {
20719
+ stack.push(dir);
20720
+ dir = cdInto(dir, words);
20721
+ continue;
20722
+ }
20723
+ if (POPD2.has(name)) {
20724
+ dir = stack.length ? stack.pop() : undefined;
20725
+ continue;
20726
+ }
20727
+ for (const file of segment.redirects)
20728
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20729
+ const resolved = at(file);
20730
+ if (resolved)
20731
+ targets.push(resolved);
20732
+ }
20733
+ const edits = editsOf(segment.words);
20734
+ if (edits === null) {
20735
+ if (dir !== undefined)
20736
+ targets.push(path23.join(dir, DIR_TARGET));
20737
+ } else
20738
+ for (const file of edits) {
20739
+ const resolved = at(file);
20740
+ if (resolved)
20741
+ targets.push(resolved);
20742
+ }
20743
+ 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), "")))
20744
+ targets.push(path23.join(dir, DIR_TARGET));
20745
+ }
20746
+ return targets;
20747
+ };
20748
+ var overridden = (command, dialect) => {
20749
+ const segments = segmentsOf(command, dialect);
20750
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20751
+ return false;
20752
+ const words = segments[0].words;
20753
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20754
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20755
+ };
20756
+ var HOST_LABELS = {
20757
+ claude_code: "Claude Code",
20758
+ opencode: "OpenCode",
20759
+ codex_cli: "Codex CLI",
20760
+ codex_desktop: "Codex Desktop",
20761
+ cursor: "Cursor",
20762
+ pi: "Pi"
20763
+ };
20764
+ var ageText = (ms) => {
20765
+ const seconds = Math.max(0, Math.round(ms / 1000));
20766
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20767
+ };
20768
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20769
+ 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}.`;
20770
+ return {
20771
+ kind: "deny",
20772
+ 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}`,
20773
+ unblock
20774
+ };
20775
+ };
20776
+ var guarded = (targets) => {
20777
+ const seen = new Set;
20778
+ return targets.filter((target) => {
20779
+ const dir = path23.dirname(target);
20780
+ if (seen.has(dir))
20781
+ return false;
20782
+ seen.add(dir);
20783
+ return true;
20784
+ });
20785
+ };
20786
+ var liveGate = (owner, targets, deps) => {
20787
+ try {
20788
+ for (const target of guarded(targets)) {
20789
+ const conflict = liveConflict(target, owner, deps);
20790
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20791
+ return liveDeny(conflict);
20792
+ }
20793
+ } catch {}
20794
+ return NONE3;
20795
+ };
20796
+ var claimAll = (owner, targets, deps) => {
20797
+ for (const target of guarded(targets))
20798
+ claimLiveSession(target, owner, deps);
20799
+ };
20800
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
20801
+ function writeGate(cwd, paths, owner, deps) {
20802
+ if (!owner)
20803
+ return beforeWriteGate(cwd, paths);
20804
+ const targets = liveTargets(cwd, paths);
20805
+ const live = liveGate(owner, targets, deps);
20806
+ if (live.kind !== "none")
20807
+ return live;
20808
+ const decision = beforeWriteGate(cwd, paths);
20809
+ if (decision.kind === "none")
20810
+ claimAll(owner, targets, deps);
20811
+ return decision;
20812
+ }
20813
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20814
+ const writes = shellWrites(command);
20815
+ let targets = [];
20816
+ if (owner) {
20817
+ try {
20818
+ targets = [
20819
+ ...segmentTargets(cwd, command, dialect),
20820
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20821
+ ];
20822
+ } catch {
20823
+ targets = [];
20824
+ }
20825
+ }
20826
+ if (owner && targets.length) {
20827
+ const live = liveGate(owner, targets, deps);
20828
+ if (live.kind !== "none") {
20829
+ if (!overridden(command, dialect))
20830
+ return live;
20831
+ noteOverride(cwd, owner, command);
20832
+ }
20833
+ }
20834
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20835
+ if (owner && targets.length && decision.kind === "none")
20836
+ claimAll(owner, targets, deps);
20837
+ return decision;
20838
+ }
20839
+ var noteOverride = (cwd, owner, command) => {
20840
+ try {
20841
+ if (!inWorkitWorkspace(cwd))
20842
+ return;
20843
+ appendHookObserved(cwd, {
20844
+ type: "live_session.override",
20845
+ actor: { host: owner.host, session: owner.session, agentId: null },
20846
+ session: owner.session,
20847
+ command: command.slice(0, 200)
20848
+ });
20849
+ } catch {}
20850
+ };
20851
+ function beforeWriteGate(cwd, paths) {
20380
20852
  try {
20381
20853
  const gate = gateFor(cwd);
20382
20854
  if (!gate || gate.blockers.length === 0)
@@ -20394,7 +20866,7 @@ function writeGate(cwd, paths) {
20394
20866
  }
20395
20867
  }
20396
20868
  // packages/workit-core/src/hooks/stop.ts
20397
- import { spawnSync as spawnSync13 } from "node:child_process";
20869
+ import { spawnSync as spawnSync14 } from "node:child_process";
20398
20870
  var NONE4 = { kind: "none" };
20399
20871
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20400
20872
  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 +20887,7 @@ var asksUser = (message) => {
20415
20887
  };
20416
20888
  var short = (sha) => (sha ?? "?").slice(0, 12);
20417
20889
  var gitList = (cwd, args) => {
20418
- const run = spawnSync13("git", args, {
20890
+ const run = spawnSync14("git", args, {
20419
20891
  cwd,
20420
20892
  encoding: "utf8",
20421
20893
  timeout: 3000,
@@ -20779,12 +21251,12 @@ ${line}` };
20779
21251
  };
20780
21252
  // packages/workit-core/src/hooks/hosts/fields.ts
20781
21253
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20782
- import path23 from "node:path";
21254
+ import path24 from "node:path";
20783
21255
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20784
21256
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20785
21257
  var optionalText = (value) => typeof value === "string" ? value : null;
20786
21258
  var existingDirectory = (value) => {
20787
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21259
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20788
21260
  return null;
20789
21261
  try {
20790
21262
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20929,8 +21401,7 @@ function handleHook(input, deps) {
20929
21401
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20930
21402
  if (raw.kind === "deny")
20931
21403
  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;
21404
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20934
21405
  if (gate.kind === "deny")
20935
21406
  return gate;
20936
21407
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20943,7 +21414,7 @@ function handleHook(input, deps) {
20943
21414
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20944
21415
  return NONE5;
20945
21416
  case "write.pre":
20946
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21417
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20947
21418
  case "subagent.start":
20948
21419
  return {
20949
21420
  kind: "context",
@@ -21522,8 +21993,8 @@ var codexAdapter = {
21522
21993
  };
21523
21994
 
21524
21995
  // packages/workit-core/src/hooks/hosts/cursor.ts
21525
- import fs17 from "node:fs";
21526
- import path24 from "node:path";
21996
+ import fs18 from "node:fs";
21997
+ import path25 from "node:path";
21527
21998
  var undocumented = { support: "undocumented", native: null };
21528
21999
  var none = { support: "none", native: null };
21529
22000
  var CURSOR_DESCRIPTOR = {
@@ -21720,17 +22191,17 @@ var protocolEvent2 = (input) => {
21720
22191
  };
21721
22192
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21722
22193
  var lastAssistantText = (file) => {
21723
- if (!file || !path24.isAbsolute(file))
22194
+ if (!file || !path25.isAbsolute(file))
21724
22195
  return null;
21725
22196
  try {
21726
- const size = fs17.statSync(file).size;
22197
+ const size = fs18.statSync(file).size;
21727
22198
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21728
22199
  const buffer = Buffer.alloc(size - start);
21729
- const fd = fs17.openSync(file, "r");
22200
+ const fd = fs18.openSync(file, "r");
21730
22201
  try {
21731
- fs17.readSync(fd, buffer, 0, buffer.length, start);
22202
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21732
22203
  } finally {
21733
- fs17.closeSync(fd);
22204
+ fs18.closeSync(fd);
21734
22205
  }
21735
22206
  const lines = buffer.toString("utf8").split(`
21736
22207
  `);
@@ -21757,7 +22228,7 @@ var lastAssistantText = (file) => {
21757
22228
  return null;
21758
22229
  }
21759
22230
  };
21760
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`);
22231
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
21761
22232
  var hookCwd = (input) => {
21762
22233
  const cwd = existingDirectory(input.cwd);
21763
22234
  if (cwd && input.hook_event_name === "beforeShellExecution")