@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.
@@ -2,7 +2,7 @@
2
2
  "name": "workit",
3
3
  "displayName": "Workit",
4
4
  "description": "Adaptive Workit workflow with shared MCP tools and native Cursor hooks",
5
- "version": "8.13.1",
5
+ "version": "8.14.0",
6
6
  "author": {
7
7
  "name": "Cristhofer Pincetti"
8
8
  },
@@ -9570,24 +9570,26 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
9570
9570
  }, resolveRuntimeReleaseTracks = (cwd) => {
9571
9571
  const matched = matchRuntimeWorkspace(cwd);
9572
9572
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
9573
- }, workspaceTrailers = (cwd, dir) => {
9573
+ }, inheritedSetting = (cwd, dir, has) => {
9574
9574
  let matched;
9575
9575
  try {
9576
9576
  matched = matchRuntimeWorkspace(cwd, dir);
9577
9577
  } catch {
9578
- return { enabled: true, workspace: null, from: null };
9578
+ return null;
9579
9579
  }
9580
9580
  if (!matched)
9581
- return { enabled: true, workspace: null, from: null };
9581
+ return null;
9582
9582
  const { selected, matches } = matched;
9583
9583
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
9584
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
9584
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
9585
+ }, workspaceTrailers = (cwd, dir) => {
9586
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
9585
9587
  return {
9586
- enabled: source?.trailers !== false,
9587
- workspace: selected.name,
9588
- from: source?.name ?? null
9588
+ enabled: found?.source?.trailers !== false,
9589
+ workspace: found?.workspace ?? null,
9590
+ from: found?.source?.name ?? null
9589
9591
  };
9590
- }, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
9592
+ }, workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false, TIER_KEYS, TIER_HOSTS, resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
9591
9593
  const matched = matchRuntimeWorkspace(cwd);
9592
9594
  if (!matched)
9593
9595
  return null;
@@ -9698,6 +9700,8 @@ var init_workspaces = __esm(() => {
9698
9700
  defaultEndpoint: unknown().optional(),
9699
9701
  verification: _enum(["self", "independent"]).optional(),
9700
9702
  trailers: boolean2().optional(),
9703
+ liveSessionGuard: boolean2().optional(),
9704
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
9701
9705
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
9702
9706
  kind,
9703
9707
  union([boolean2(), literal("verified")]).optional()
@@ -9747,6 +9751,17 @@ var init_workspaces = __esm(() => {
9747
9751
  runtimeWorkspaceIndexSchema = object({
9748
9752
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
9749
9753
  }).passthrough();
9754
+ TIER_KEYS = new Set(["mundane", "standard", "hard"]);
9755
+ TIER_HOSTS = new Set([
9756
+ "claude_code",
9757
+ "opencode",
9758
+ "codex",
9759
+ "codex_cli",
9760
+ "codex_desktop",
9761
+ "cursor",
9762
+ "pi",
9763
+ "default"
9764
+ ]);
9750
9765
  });
9751
9766
 
9752
9767
  // packages/workit-core/src/core/release-tracks.ts
@@ -20052,10 +20067,238 @@ var init_policy = __esm(() => {
20052
20067
  init_shell_words();
20053
20068
  });
20054
20069
 
20070
+ // packages/workit-core/src/core/live-session.ts
20071
+ import { spawnSync as spawnSync13 } from "node:child_process";
20072
+ import { createHash as createHash15 } from "node:crypto";
20073
+ import fs17 from "node:fs";
20074
+ import os6 from "node:os";
20075
+ import path22 from "node:path";
20076
+ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session, familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, ""), processAlive = (pid) => {
20077
+ try {
20078
+ process.kill(pid, 0);
20079
+ return true;
20080
+ } catch (error) {
20081
+ return error.code === "EPERM";
20082
+ }
20083
+ }, processInfo = (pid) => {
20084
+ try {
20085
+ if (liveProbe.platform === "linux") {
20086
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20087
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20088
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20089
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20090
+ }
20091
+ if (liveProbe.platform === "win32")
20092
+ return null;
20093
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20094
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20095
+ } catch {
20096
+ return null;
20097
+ }
20098
+ }, WRAPPER, PIDS, hostPid = (transport) => {
20099
+ if (transport !== "hook-process")
20100
+ return process.pid;
20101
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20102
+ if (PIDS.has(key))
20103
+ return PIDS.get(key) ?? null;
20104
+ if (liveProbe.platform === "win32") {
20105
+ PIDS.set(key, null);
20106
+ return null;
20107
+ }
20108
+ let pid = process.ppid;
20109
+ for (let hop = 0;hop < 4; hop++) {
20110
+ const info = processInfo(pid);
20111
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20112
+ break;
20113
+ pid = info.ppid;
20114
+ }
20115
+ PIDS.set(key, pid);
20116
+ return pid;
20117
+ }, sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-"), liveOwner = (input, transport, env = process.env) => {
20118
+ const session = input.session.id?.trim();
20119
+ if (!session)
20120
+ return null;
20121
+ const leads = new Set([leadOf(sessionSafe(session))]);
20122
+ if (input.session.parentId)
20123
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20124
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20125
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20126
+ return {
20127
+ host: input.host,
20128
+ session: sessionSafe(session),
20129
+ leads: [...leads],
20130
+ pid: () => hostPid(transport)
20131
+ };
20132
+ }, checkoutOf = (target) => {
20133
+ let dir = path22.resolve(target);
20134
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20135
+ dir = path22.dirname(dir);
20136
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20137
+ dir = path22.dirname(dir);
20138
+ const parts = dir.split(path22.sep);
20139
+ const dotGit = parts.lastIndexOf(".git");
20140
+ if (dotGit > 0)
20141
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20142
+ const location = resolveStore(dir);
20143
+ if (location instanceof Error || !location.shared || !location.top)
20144
+ return null;
20145
+ let top = location.top;
20146
+ try {
20147
+ top = fs17.realpathSync.native(top);
20148
+ } catch {}
20149
+ return top;
20150
+ }, markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`), checkoutKey = (top, platform = process.platform) => {
20151
+ let key = path22.resolve(top);
20152
+ try {
20153
+ key = fs17.realpathSync.native(key);
20154
+ } catch {}
20155
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20156
+ }, readMarker = (file) => {
20157
+ try {
20158
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20159
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20160
+ return null;
20161
+ return {
20162
+ version: 1,
20163
+ session: value.session,
20164
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20165
+ host: value.host,
20166
+ pid: typeof value.pid === "number" ? value.pid : null,
20167
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20168
+ machine: typeof value.machine === "string" ? value.machine : "",
20169
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20170
+ heartbeatAt: value.heartbeatAt
20171
+ };
20172
+ } catch {
20173
+ return null;
20174
+ }
20175
+ }, ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt), isLive = (marker, deps) => {
20176
+ const age = ageOf2(marker, deps.now ?? new Date);
20177
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20178
+ return false;
20179
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20180
+ return true;
20181
+ if (!(deps.alive ?? processAlive)(marker.pid))
20182
+ return false;
20183
+ if (marker.pidStart === null)
20184
+ return true;
20185
+ const start = liveProbe.start(marker.pid);
20186
+ return start === null || start === marker.pidStart;
20187
+ }, sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead)), sameOwner = (marker, owner, machine) => {
20188
+ if (sameLead(marker, owner))
20189
+ return true;
20190
+ if (marker.machine !== machine)
20191
+ return false;
20192
+ const pid = owner.pid();
20193
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20194
+ return familyOf(marker.host) === familyOf(owner.host);
20195
+ return marker.pid === pid;
20196
+ }, liveConflict = (target, owner, deps = {}) => {
20197
+ try {
20198
+ const checkout = checkoutOf(target);
20199
+ if (!checkout)
20200
+ return null;
20201
+ const file = markerFile2(checkout);
20202
+ const marker = readMarker(file);
20203
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20204
+ return null;
20205
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20206
+ return null;
20207
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20208
+ } catch {
20209
+ return null;
20210
+ }
20211
+ }, claimLiveSession = (target, owner, deps = {}) => {
20212
+ try {
20213
+ const checkout = checkoutOf(target);
20214
+ if (!checkout)
20215
+ return;
20216
+ const file = markerFile2(checkout);
20217
+ const now = deps.now ?? new Date;
20218
+ const machine = deps.machine ?? os6.hostname();
20219
+ const current = readMarker(file);
20220
+ if (current) {
20221
+ const age = ageOf2(current, now);
20222
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20223
+ return;
20224
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20225
+ return;
20226
+ }
20227
+ const pid = owner.pid();
20228
+ const marker = {
20229
+ version: 1,
20230
+ session: owner.session,
20231
+ leads: owner.leads,
20232
+ host: owner.host,
20233
+ pid,
20234
+ pidStart: pid === null ? null : liveProbe.start(pid),
20235
+ machine,
20236
+ checkout,
20237
+ heartbeatAt: now.toISOString()
20238
+ };
20239
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20240
+ const temp = `${file}.${process.pid}.tmp`;
20241
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20242
+ fs17.renameSync(temp, file);
20243
+ } catch {}
20244
+ };
20245
+ var init_live_session = __esm(() => {
20246
+ init_paths();
20247
+ init_logger();
20248
+ init_store_lock();
20249
+ LIVE_SESSION_TTL_MS = 15 * 60000;
20250
+ liveProbe = {
20251
+ platform: process.platform,
20252
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20253
+ start: (pid) => processStartOf(pid)
20254
+ };
20255
+ 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;
20256
+ PIDS = new Map;
20257
+ });
20258
+
20055
20259
  // packages/workit-core/src/hooks/write-gate.ts
20056
20260
  import { realpathSync as realpathSync5 } from "node:fs";
20057
- import path22 from "node:path";
20058
- function writeGate(cwd, paths) {
20261
+ import os7 from "node:os";
20262
+ import path23 from "node:path";
20263
+ function writeGate(cwd, paths, owner, deps) {
20264
+ if (!owner)
20265
+ return beforeWriteGate(cwd, paths);
20266
+ const targets = liveTargets(cwd, paths);
20267
+ const live = liveGate(owner, targets, deps);
20268
+ if (live.kind !== "none")
20269
+ return live;
20270
+ const decision = beforeWriteGate(cwd, paths);
20271
+ if (decision.kind === "none")
20272
+ claimAll(owner, targets, deps);
20273
+ return decision;
20274
+ }
20275
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20276
+ const writes = shellWrites(command);
20277
+ let targets = [];
20278
+ if (owner) {
20279
+ try {
20280
+ targets = [
20281
+ ...segmentTargets(cwd, command, dialect),
20282
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20283
+ ];
20284
+ } catch {
20285
+ targets = [];
20286
+ }
20287
+ }
20288
+ if (owner && targets.length) {
20289
+ const live = liveGate(owner, targets, deps);
20290
+ if (live.kind !== "none") {
20291
+ if (!overridden(command, dialect))
20292
+ return live;
20293
+ noteOverride(cwd, owner, command);
20294
+ }
20295
+ }
20296
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20297
+ if (owner && targets.length && decision.kind === "none")
20298
+ claimAll(owner, targets, deps);
20299
+ return decision;
20300
+ }
20301
+ function beforeWriteGate(cwd, paths) {
20059
20302
  try {
20060
20303
  const gate = gateFor(cwd);
20061
20304
  if (!gate || gate.blockers.length === 0)
@@ -20074,21 +20317,21 @@ function writeGate(cwd, paths) {
20074
20317
  }
20075
20318
  var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20076
20319
  let rest = "";
20077
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20320
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20078
20321
  try {
20079
- return path22.join(realpathSync5(dir), rest);
20322
+ return path23.join(realpathSync5(dir), rest);
20080
20323
  } catch {
20081
- if (path22.dirname(dir) === dir)
20082
- return path22.resolve(file);
20083
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20324
+ if (path23.dirname(dir) === dir)
20325
+ return path23.resolve(file);
20326
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20084
20327
  }
20085
20328
  }
20086
20329
  }, exempt = (cwd, root, file, plans) => {
20087
20330
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20088
- const relative = path22.relative(fold(canonical2(root)), fold(canonical2(path22.resolve(cwd, file))));
20089
- if (relative.startsWith("..") || path22.isAbsolute(relative))
20331
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20332
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20090
20333
  return true;
20091
- const posix = relative.split(path22.sep).join("/");
20334
+ const posix = relative.split(path23.sep).join("/");
20092
20335
  const segments = posix.split("/");
20093
20336
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20094
20337
  }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
@@ -20180,7 +20423,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20180
20423
  if (!unknown && targets.length === 0)
20181
20424
  return { writes: false, targets: null };
20182
20425
  return { writes: true, targets: unknown ? null : targets };
20183
- }, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
20426
+ }, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
20184
20427
  const store = new TaskStore(cwd);
20185
20428
  const listed = store.listTaskIndex();
20186
20429
  if (!listed.ok)
@@ -20216,21 +20459,182 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20216
20459
  if (cache2.size > 32)
20217
20460
  cache2.delete(cache2.keys().next().value);
20218
20461
  return gate;
20462
+ }, MUTATING_GIT, DRY_N, MUTATING_WORKIT_GIT, DIR_TARGET = ".workit-live-target", OVERRIDE = "WORKIT_LIVE_SESSION=override", 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("--")), gitMutationDirs = (cwd, command, dialect) => {
20463
+ let invocations;
20464
+ try {
20465
+ invocations = rawInvocations(command, dialect);
20466
+ } catch {
20467
+ return [];
20468
+ }
20469
+ return invocations.flatMap((invocation) => {
20470
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20471
+ return [];
20472
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20473
+ const sub = invocation.args[at] ?? "";
20474
+ const rest = invocation.args.slice(at + 1);
20475
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20476
+ return [];
20477
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20478
+ return [];
20479
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20480
+ });
20481
+ }, CD2, PUSHD2, POPD2, cdInto = (dir, words) => {
20482
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20483
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20484
+ return;
20485
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20486
+ }, quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, segmentTargets = (cwd, command, dialect) => {
20487
+ const targets = [];
20488
+ let dir = cwd;
20489
+ let stack = [];
20490
+ const groups = [];
20491
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20492
+ for (const segment of segmentsOf(command, dialect)) {
20493
+ if (segment.group === "open") {
20494
+ groups.push({ dir, stack: [...stack] });
20495
+ continue;
20496
+ }
20497
+ if (segment.group === "close") {
20498
+ const saved = groups.pop();
20499
+ if (saved)
20500
+ ({ dir, stack } = saved);
20501
+ continue;
20502
+ }
20503
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20504
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20505
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20506
+ if (CD2.has(name)) {
20507
+ dir = cdInto(dir, words);
20508
+ continue;
20509
+ }
20510
+ if (PUSHD2.has(name)) {
20511
+ stack.push(dir);
20512
+ dir = cdInto(dir, words);
20513
+ continue;
20514
+ }
20515
+ if (POPD2.has(name)) {
20516
+ dir = stack.length ? stack.pop() : undefined;
20517
+ continue;
20518
+ }
20519
+ for (const file of segment.redirects)
20520
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20521
+ const resolved = at(file);
20522
+ if (resolved)
20523
+ targets.push(resolved);
20524
+ }
20525
+ const edits = editsOf(segment.words);
20526
+ if (edits === null) {
20527
+ if (dir !== undefined)
20528
+ targets.push(path23.join(dir, DIR_TARGET));
20529
+ } else
20530
+ for (const file of edits) {
20531
+ const resolved = at(file);
20532
+ if (resolved)
20533
+ targets.push(resolved);
20534
+ }
20535
+ 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), "")))
20536
+ targets.push(path23.join(dir, DIR_TARGET));
20537
+ }
20538
+ return targets;
20539
+ }, overridden = (command, dialect) => {
20540
+ const segments = segmentsOf(command, dialect);
20541
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20542
+ return false;
20543
+ const words = segments[0].words;
20544
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20545
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20546
+ }, HOST_LABELS, ageText = (ms) => {
20547
+ const seconds = Math.max(0, Math.round(ms / 1000));
20548
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20549
+ }, liveDeny = ({ marker, checkout, ageMs, file }) => {
20550
+ 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}.`;
20551
+ return {
20552
+ kind: "deny",
20553
+ 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}`,
20554
+ unblock
20555
+ };
20556
+ }, guarded = (targets) => {
20557
+ const seen = new Set;
20558
+ return targets.filter((target) => {
20559
+ const dir = path23.dirname(target);
20560
+ if (seen.has(dir))
20561
+ return false;
20562
+ seen.add(dir);
20563
+ return true;
20564
+ });
20565
+ }, liveGate = (owner, targets, deps) => {
20566
+ try {
20567
+ for (const target of guarded(targets)) {
20568
+ const conflict = liveConflict(target, owner, deps);
20569
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20570
+ return liveDeny(conflict);
20571
+ }
20572
+ } catch {}
20573
+ return NONE3;
20574
+ }, claimAll = (owner, targets, deps) => {
20575
+ for (const target of guarded(targets))
20576
+ claimLiveSession(target, owner, deps);
20577
+ }, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)], noteOverride = (cwd, owner, command) => {
20578
+ try {
20579
+ if (!inWorkitWorkspace(cwd))
20580
+ return;
20581
+ appendHookObserved(cwd, {
20582
+ type: "live_session.override",
20583
+ actor: { host: owner.host, session: owner.session, agentId: null },
20584
+ session: owner.session,
20585
+ command: command.slice(0, 200)
20586
+ });
20587
+ } catch {}
20219
20588
  };
20220
20589
  var init_write_gate = __esm(() => {
20221
20590
  init_task_store();
20222
20591
  init_derive();
20223
20592
  init_task_evaluation();
20593
+ init_live_session();
20594
+ init_workspaces();
20224
20595
  init_ledger();
20596
+ init_raw_git();
20225
20597
  init_shell_words();
20226
20598
  NONE3 = { kind: "none" };
20227
20599
  CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20228
20600
  PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20229
20601
  cache2 = new Map;
20602
+ MUTATING_GIT = new Set([
20603
+ "add",
20604
+ "am",
20605
+ "apply",
20606
+ "checkout",
20607
+ "cherry-pick",
20608
+ "clean",
20609
+ "commit",
20610
+ "merge",
20611
+ "mv",
20612
+ "pull",
20613
+ "rebase",
20614
+ "reset",
20615
+ "restore",
20616
+ "revert",
20617
+ "rm",
20618
+ "stash",
20619
+ "switch"
20620
+ ]);
20621
+ DRY_N = new Set(["add", "clean", "rm", "mv"]);
20622
+ MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20623
+ CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20624
+ PUSHD2 = new Set(["pushd", "push-location"]);
20625
+ POPD2 = new Set(["popd", "pop-location"]);
20626
+ HOST_LABELS = {
20627
+ claude_code: "Claude Code",
20628
+ opencode: "OpenCode",
20629
+ codex_cli: "Codex CLI",
20630
+ codex_desktop: "Codex Desktop",
20631
+ cursor: "Cursor",
20632
+ pi: "Pi"
20633
+ };
20230
20634
  });
20231
20635
 
20232
20636
  // packages/workit-core/src/hooks/stop.ts
20233
- import { spawnSync as spawnSync13 } from "node:child_process";
20637
+ import { spawnSync as spawnSync14 } from "node:child_process";
20234
20638
  function stopObligation(input, descriptor) {
20235
20639
  try {
20236
20640
  const session = input.session.id;
@@ -20294,7 +20698,7 @@ var NONE4, QUESTION, OFFER, YES_NO, LIST_ITEM, asksUser = (message) => {
20294
20698
  `);
20295
20699
  return QUESTION.test(tail) || OFFER.test(tail) || YES_NO.test(tail);
20296
20700
  }, short = (sha) => (sha ?? "?").slice(0, 12), gitList = (cwd, args) => {
20297
- const run = spawnSync13("git", args, {
20701
+ const run = spawnSync14("git", args, {
20298
20702
  cwd,
20299
20703
  encoding: "utf8",
20300
20704
  timeout: 3000,
@@ -20606,9 +21010,9 @@ var init_skill_nudge = __esm(() => {
20606
21010
 
20607
21011
  // packages/workit-core/src/hooks/hosts/fields.ts
20608
21012
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20609
- import path23 from "node:path";
21013
+ import path24 from "node:path";
20610
21014
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
20611
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21015
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20612
21016
  return null;
20613
21017
  try {
20614
21018
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20713,8 +21117,7 @@ function handleHook(input, deps) {
20713
21117
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20714
21118
  if (raw.kind === "deny")
20715
21119
  return raw;
20716
- const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
20717
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
21120
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20718
21121
  if (gate.kind === "deny")
20719
21122
  return gate;
20720
21123
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20727,7 +21130,7 @@ function handleHook(input, deps) {
20727
21130
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20728
21131
  return NONE5;
20729
21132
  case "write.pre":
20730
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21133
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20731
21134
  case "subagent.start":
20732
21135
  return {
20733
21136
  kind: "context",
@@ -20810,6 +21213,7 @@ var init_handle = __esm(() => {
20810
21213
  init_raw_git();
20811
21214
  init_skill_nudge();
20812
21215
  init_stop();
21216
+ init_live_session();
20813
21217
  init_write_gate();
20814
21218
  NONE5 = { kind: "none" };
20815
21219
  offered = new Set;
@@ -21348,8 +21752,8 @@ var init_codex = __esm(() => {
21348
21752
  });
21349
21753
 
21350
21754
  // packages/workit-core/src/hooks/hosts/cursor.ts
21351
- import fs17 from "node:fs";
21352
- import path24 from "node:path";
21755
+ import fs18 from "node:fs";
21756
+ import path25 from "node:path";
21353
21757
  var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
21354
21758
  if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
21355
21759
  return { ok: false, error: "hook_event_name is required" };
@@ -21430,17 +21834,17 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
21430
21834
  };
21431
21835
  }
21432
21836
  }, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
21433
- if (!file || !path24.isAbsolute(file))
21837
+ if (!file || !path25.isAbsolute(file))
21434
21838
  return null;
21435
21839
  try {
21436
- const size = fs17.statSync(file).size;
21840
+ const size = fs18.statSync(file).size;
21437
21841
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21438
21842
  const buffer = Buffer.alloc(size - start);
21439
- const fd = fs17.openSync(file, "r");
21843
+ const fd = fs18.openSync(file, "r");
21440
21844
  try {
21441
- fs17.readSync(fd, buffer, 0, buffer.length, start);
21845
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21442
21846
  } finally {
21443
- fs17.closeSync(fd);
21847
+ fs18.closeSync(fd);
21444
21848
  }
21445
21849
  const lines = buffer.toString("utf8").split(`
21446
21850
  `);
@@ -21466,7 +21870,7 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
21466
21870
  } catch {
21467
21871
  return null;
21468
21872
  }
21469
- }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`), hookCwd = (input) => {
21873
+ }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`), hookCwd = (input) => {
21470
21874
  const cwd = existingDirectory(input.cwd);
21471
21875
  if (cwd && input.hook_event_name === "beforeShellExecution")
21472
21876
  return cwd;
@@ -21692,6 +22096,7 @@ var init_hooks = __esm(async () => {
21692
22096
  init_context();
21693
22097
  init_policy();
21694
22098
  init_write_gate();
22099
+ init_live_session();
21695
22100
  init_stop();
21696
22101
  init_raw_git();
21697
22102
  init_skill_nudge();