@brainervirus/workit-cursor 8.1.0 → 8.3.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.
@@ -8572,9 +8572,27 @@ function patchId(cwd, base, head) {
8572
8572
  const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
8573
8573
  return line(id)?.split(/\s+/u)[0] ?? null;
8574
8574
  }
8575
+ function remoteNames(cwd) {
8576
+ return (git(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
8577
+ }
8575
8578
  function currentBranch(cwd) {
8576
8579
  return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
8577
8580
  }
8581
+ function pushRemoteName(cwd, branch) {
8582
+ const name = branch ?? currentBranch(cwd);
8583
+ const remotes = remoteNames(cwd);
8584
+ const candidates = [
8585
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
8586
+ config2(cwd, "remote.pushDefault"),
8587
+ name ? config2(cwd, `branch.${name}.remote`) : null
8588
+ ];
8589
+ for (const candidate of candidates)
8590
+ if (candidate && candidate !== "." && remotes.includes(candidate))
8591
+ return candidate;
8592
+ if (remotes.includes("origin"))
8593
+ return "origin";
8594
+ return remotes.length === 1 ? remotes[0] : null;
8595
+ }
8578
8596
  var MAX_BUFFER, GIT_TIMEOUTS, git = (cwd, args, options = {}) => {
8579
8597
  const result = spawnSync("git", args, {
8580
8598
  cwd,
@@ -8603,13 +8621,44 @@ var MAX_BUFFER, GIT_TIMEOUTS, git = (cwd, args, options = {}) => {
8603
8621
  const { atimeNs, mtimeNs } = fs.statSync(from, { bigint: true });
8604
8622
  const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
8605
8623
  fs.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
8606
- };
8624
+ }, config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
8607
8625
  var init_rev = __esm(() => {
8608
8626
  MAX_BUFFER = 256 * 1024 * 1024;
8609
8627
  GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
8610
8628
  MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
8611
8629
  });
8612
8630
 
8631
+ // packages/workit-core/src/host-session.ts
8632
+ function hostSessionFromEnv(env) {
8633
+ const value = (key) => env[key]?.trim() || null;
8634
+ let inner = null;
8635
+ for (const [key, host] of HOST_SESSION_VARS) {
8636
+ const session = value(key);
8637
+ if (session) {
8638
+ inner = { host: host(env), session };
8639
+ break;
8640
+ }
8641
+ }
8642
+ const declared = value("WORKIT_HOST");
8643
+ if (env.WORKIT_SESSION_ID !== undefined) {
8644
+ const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
8645
+ if (!inherited)
8646
+ return { host: declared, session: value("WORKIT_SESSION_ID") };
8647
+ }
8648
+ return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
8649
+ }
8650
+ var HOST_SESSION_VARS, family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
8651
+ var init_host_session = __esm(() => {
8652
+ HOST_SESSION_VARS = [
8653
+ ["OPENCODE_SESSION_ID", () => "opencode"],
8654
+ ["PI_SESSION_ID", () => "pi"],
8655
+ [
8656
+ "CODEX_THREAD_ID",
8657
+ (env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
8658
+ ]
8659
+ ];
8660
+ });
8661
+
8613
8662
  // packages/workit-core/src/store/paths.ts
8614
8663
  import { spawnSync as spawnSync2 } from "node:child_process";
8615
8664
  import { createHash as createHash3 } from "node:crypto";
@@ -8952,6 +9001,74 @@ function ledgerPath(cwd) {
8952
9001
  const root = storeRoot(cwd);
8953
9002
  return root.ok ? { ok: true, value: path3.join(root.value.root, "ledger", "ledger.jsonl") } : root;
8954
9003
  }
9004
+ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
9005
+ const lock = `${file}.lock`;
9006
+ const deadline = Date.now() + waitMs;
9007
+ let token = null;
9008
+ for (;; ) {
9009
+ token = ledgerLock.acquire(lock);
9010
+ if (token)
9011
+ break;
9012
+ const observed = lockToken(lock);
9013
+ const age = lockAgeMs(lock);
9014
+ if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
9015
+ continue;
9016
+ if (Date.now() >= deadline)
9017
+ return null;
9018
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
9019
+ }
9020
+ try {
9021
+ return fn();
9022
+ } finally {
9023
+ ledgerLock.release(lock, token);
9024
+ }
9025
+ }
9026
+ function appendLine(file, line, options) {
9027
+ const write = () => {
9028
+ const fd = fs3.openSync(file, "a+");
9029
+ try {
9030
+ const size = fs3.fstatSync(fd).size;
9031
+ let fence = "";
9032
+ if (size > 0) {
9033
+ const last = Buffer.alloc(1);
9034
+ fs3.readSync(fd, last, 0, 1, size - 1);
9035
+ if (last[0] !== 10)
9036
+ fence = `
9037
+ `;
9038
+ }
9039
+ const bytes = Buffer.from(fence + line);
9040
+ const written = fs3.writeSync(fd, bytes);
9041
+ if (written !== bytes.length)
9042
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
9043
+ return { ok: true, value: undefined };
9044
+ } finally {
9045
+ fs3.closeSync(fd);
9046
+ }
9047
+ };
9048
+ try {
9049
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
9050
+ if (!options.forceLock && !onNetworkFs(path3.dirname(file)))
9051
+ return write();
9052
+ const locked = withLedgerLock(file, write);
9053
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
9054
+ } catch (error) {
9055
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
9056
+ }
9057
+ }
9058
+ function appendRaw(cwd, row, options) {
9059
+ const { full, line } = serialize(row, options.now ?? new Date);
9060
+ const bytes = Buffer.byteLength(line);
9061
+ if (bytes > MAX_LINE_BYTES)
9062
+ return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
9063
+ const file = ledgerPath(cwd);
9064
+ if (!file.ok)
9065
+ return file;
9066
+ const written = appendLine(file.value, line, options);
9067
+ return written.ok ? { ok: true, value: full } : written;
9068
+ }
9069
+ function appendHookObserved(cwd, row) {
9070
+ return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
9071
+ }
8955
9072
  function supersedeAllowed(by, target) {
8956
9073
  if (by.type !== target.type)
8957
9074
  return `a ${by.type} row cannot supersede a ${target.type} row`;
@@ -9169,7 +9286,7 @@ function checkVerdicts(cwd, branch, rows) {
9169
9286
  authors: [...authors].toSorted()
9170
9287
  };
9171
9288
  }
9172
- var LEDGER_VERSION = 1, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (code, error, unblock) => ({
9289
+ var LEDGER_VERSION = 1, MAX_LINE_BYTES = 4096, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (code, error, unblock) => ({
9173
9290
  ok: false,
9174
9291
  code,
9175
9292
  error,
@@ -9199,7 +9316,32 @@ var LEDGER_VERSION = 1, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (c
9199
9316
  const run = gitRun2(cwd, args);
9200
9317
  const value = run.ok ? run.stdout.trim() : "";
9201
9318
  return value ? value : null;
9202
- }, safeRef = (value) => value.length > 0 && !value.startsWith("-"), NETWORK_FS_TYPES, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
9319
+ }, safeRef = (value) => value.length > 0 && !value.startsWith("-"), newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`, NETWORK_FS_TYPES, isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0), onNetworkFs = (dir) => {
9320
+ if (process.platform !== "linux")
9321
+ return false;
9322
+ try {
9323
+ return isNetworkFsType(fs3.statfsSync(dir).type);
9324
+ } catch {
9325
+ return false;
9326
+ }
9327
+ }, LOCK_WAIT_MS = 5000, LOCK_STALE_MS = 30000, LOCK_OWNER = "owner", lockToken = (lock) => {
9328
+ try {
9329
+ return fs3.readFileSync(path3.join(lock, LOCK_OWNER), "utf8");
9330
+ } catch {
9331
+ return null;
9332
+ }
9333
+ }, lockAgeMs = (lock) => {
9334
+ for (const target of [path3.join(lock, LOCK_OWNER), lock]) {
9335
+ try {
9336
+ return Date.now() - fs3.statSync(target).mtimeMs;
9337
+ } catch {}
9338
+ }
9339
+ return null;
9340
+ }, ledgerLock, serialize = (row, now) => {
9341
+ const full = { v: LEDGER_VERSION, id: newId2(), at: now.toISOString(), ...row };
9342
+ return { full, line: `${JSON.stringify(full)}
9343
+ ` };
9344
+ }, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), str = (value) => typeof value === "string" ? value : null, normalizeActor = (value) => {
9203
9345
  const actor = isRecord(value) ? value : {};
9204
9346
  return {
9205
9347
  host: str(actor.host) ?? "unknown",
@@ -9218,6 +9360,7 @@ var LEDGER_VERSION = 1, PASSING, FAILING, STRONG_RESULTS, PR_ROW_TYPES, err = (c
9218
9360
  }, OBSERVED_ONLY;
9219
9361
  var init_ledger = __esm(() => {
9220
9362
  init_rev();
9363
+ init_host_session();
9221
9364
  init_paths();
9222
9365
  PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
9223
9366
  FAILING = new Set(["failed", "blocked"]);
@@ -9235,6 +9378,57 @@ var init_ledger = __esm(() => {
9235
9378
  1196443219,
9236
9379
  198183888
9237
9380
  ]);
9381
+ ledgerLock = {
9382
+ acquire(lock) {
9383
+ try {
9384
+ fs3.mkdirSync(lock);
9385
+ } catch (error) {
9386
+ if (error.code === "EEXIST")
9387
+ return null;
9388
+ throw error;
9389
+ }
9390
+ const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
9391
+ fs3.writeFileSync(path3.join(lock, LOCK_OWNER), token);
9392
+ return token;
9393
+ },
9394
+ release(lock, token) {
9395
+ if (lockToken(lock) !== token)
9396
+ return false;
9397
+ const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
9398
+ try {
9399
+ fs3.renameSync(lock, tomb);
9400
+ } catch {
9401
+ return false;
9402
+ }
9403
+ if (lockToken(tomb) !== token) {
9404
+ try {
9405
+ fs3.renameSync(tomb, lock);
9406
+ } catch {}
9407
+ return false;
9408
+ }
9409
+ fs3.rmSync(tomb, { recursive: true, force: true });
9410
+ return true;
9411
+ },
9412
+ token: lockToken,
9413
+ reap(lock, observed) {
9414
+ const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
9415
+ try {
9416
+ fs3.renameSync(lock, tomb);
9417
+ } catch {
9418
+ return false;
9419
+ }
9420
+ if (lockToken(tomb) === observed) {
9421
+ fs3.rmSync(tomb, { recursive: true, force: true });
9422
+ return true;
9423
+ }
9424
+ try {
9425
+ fs3.renameSync(tomb, lock);
9426
+ } catch {
9427
+ fs3.rmSync(tomb, { recursive: true, force: true });
9428
+ }
9429
+ return false;
9430
+ }
9431
+ };
9238
9432
  OBSERVED_ONLY = new Set(["check", "merge.unverified", ...PR_ROW_TYPES]);
9239
9433
  });
9240
9434
 
@@ -10217,6 +10411,7 @@ var init_branch_policy = __esm(() => {
10217
10411
  });
10218
10412
 
10219
10413
  // packages/workit-core/src/core/workspaces.ts
10414
+ import { spawnSync as spawnSync5 } from "node:child_process";
10220
10415
  import { createHash as createHash5 } from "node:crypto";
10221
10416
  import { readFileSync as readFileSync2, realpathSync } from "node:fs";
10222
10417
  import path6 from "node:path";
@@ -10336,6 +10531,46 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10336
10531
  };
10337
10532
  }
10338
10533
  return { ok: true };
10534
+ }, WorkspaceConfigError, mainCheckouts, mainCheckoutOf = (cwd) => {
10535
+ const cached = mainCheckouts.get(cwd);
10536
+ if (cached !== undefined)
10537
+ return cached;
10538
+ const env = { ...process.env };
10539
+ for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
10540
+ delete env[name];
10541
+ const git = (args) => {
10542
+ const run = spawnSync5("git", args, {
10543
+ cwd,
10544
+ env,
10545
+ encoding: "utf8",
10546
+ timeout: 5000,
10547
+ windowsHide: true
10548
+ });
10549
+ return run.status === 0 ? run.stdout : null;
10550
+ };
10551
+ let main = null;
10552
+ const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
10553
+ const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
10554
+ if (gitDir && common && path6.resolve(gitDir) !== path6.resolve(common)) {
10555
+ const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
10556
+ main = listed?.trim() || (path6.basename(common) === ".git" ? path6.dirname(common) : common);
10557
+ }
10558
+ mainCheckouts.set(cwd, main);
10559
+ return main;
10560
+ }, matchingEntries = (cwd, entries) => {
10561
+ const match = (where) => {
10562
+ const targets = [where, realpathOf(where)].map((value) => value.replaceAll("\\", "/"));
10563
+ return entries.filter((entry) => {
10564
+ const glob = entry.glob.replaceAll("\\", "/");
10565
+ const canonical = canonicalGlob(glob);
10566
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
10567
+ });
10568
+ };
10569
+ const direct = match(cwd);
10570
+ if (direct.length)
10571
+ return direct;
10572
+ const main = mainCheckoutOf(cwd);
10573
+ return main ? match(main) : direct;
10339
10574
  }, realpathOf = (p) => {
10340
10575
  const real = realpathSync.native ?? realpathSync;
10341
10576
  try {
@@ -10357,41 +10592,27 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10357
10592
  if (named.length === 1)
10358
10593
  return named[0];
10359
10594
  if (named.length > 1)
10360
- throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
10595
+ throw new WorkspaceConfigError(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
10361
10596
  if (matches.length > 0) {
10362
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
10597
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
10363
10598
  }
10364
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
10599
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
10365
10600
  }
10366
10601
  if (matches.length < 2)
10367
10602
  return matches[0];
10368
10603
  const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
10369
10604
  const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
10370
10605
  if (mostSpecific.length > 1) {
10371
- throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
10606
+ throw new WorkspaceConfigError(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
10372
10607
  }
10373
10608
  return mostSpecific[0];
10374
10609
  }, resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
10375
- const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
10376
- const matches = [];
10377
- for (const ws of entries) {
10378
- const glob = ws.glob.replaceAll("\\", "/");
10379
- const canonical = canonicalGlob(glob);
10380
- let matched = false;
10381
- for (const target of targets) {
10382
- if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
10383
- matched = true;
10384
- break;
10385
- }
10386
- }
10387
- if (matched)
10388
- matches.push(ws);
10389
- }
10610
+ const matches = matchingEntries(cwd, entries);
10390
10611
  return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
10391
10612
  }, resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
10392
10613
  const result = readWorkspacesResult(dir);
10393
10614
  if (result.status === "malformed" || result.status === "invalid")
10394
- throw new Error(result.error);
10615
+ throw new WorkspaceConfigError(result.error);
10395
10616
  return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
10396
10617
  }, runtimeWorkspacePolicySchema = (kind) => {
10397
10618
  const key = kind === "branch" ? "branchPolicy" : kind === "commit" ? "commitPolicy" : "vcs";
@@ -10409,7 +10630,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10409
10630
  profiles: record(nonBlank, profileSchema).optional()
10410
10631
  }).passthrough();
10411
10632
  return entrySchema;
10412
- }, runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
10633
+ }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
10413
10634
  const file = path6.join(configDir(), "workspaces.json");
10414
10635
  let raw;
10415
10636
  try {
@@ -10417,31 +10638,26 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10417
10638
  } catch (error) {
10418
10639
  if (error.code === "ENOENT")
10419
10640
  return null;
10420
- throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
10641
+ throw new WorkspaceConfigError(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
10421
10642
  }
10422
10643
  let parsed;
10423
10644
  try {
10424
10645
  parsed = JSON.parse(raw);
10425
10646
  } catch {
10426
- throw new Error(`${file} is not valid JSON`);
10647
+ throw new WorkspaceConfigError(`${file} is not valid JSON`);
10427
10648
  }
10428
10649
  const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
10429
10650
  if (!validated.success) {
10430
10651
  const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
10431
- throw new Error(`${file} has invalid workspace matching data: ${detail}`);
10652
+ throw new WorkspaceConfigError(`${file} has invalid workspace matching data: ${detail}`);
10432
10653
  }
10433
10654
  const entries = validated.data.workspaces;
10434
10655
  for (const entry of entries ?? []) {
10435
10656
  const glob = validateWorkspaceGlob(entry.glob);
10436
10657
  if (!glob.ok)
10437
- throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
10658
+ throw new WorkspaceConfigError(`${file} workspace ${entry.name}: ${glob.error}`);
10438
10659
  }
10439
- const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
10440
- const matches = (entries ?? []).filter((entry) => {
10441
- const glob = entry.glob.replaceAll("\\", "/");
10442
- const canonical = canonicalGlob(glob);
10443
- return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
10444
- });
10660
+ const matches = matchingEntries(cwd, entries ?? []);
10445
10661
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
10446
10662
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
10447
10663
  return selected ? { file, selected } : null;
@@ -10456,7 +10672,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10456
10672
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
10457
10673
  if (!policy.success) {
10458
10674
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
10459
- throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
10675
+ throw new WorkspaceConfigError(`${file} has invalid ${kind} policy configuration: ${detail}`);
10460
10676
  }
10461
10677
  return policy.data;
10462
10678
  }, resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
@@ -10466,10 +10682,10 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10466
10682
  const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
10467
10683
  const profileName = requestedProfile?.trim() || candidate.defaultProfile;
10468
10684
  if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
10469
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
10685
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
10470
10686
  const profile = profileName ? candidate.profiles?.[profileName] : undefined;
10471
10687
  if (profileName && !profile)
10472
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
10688
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
10473
10689
  const profilePolicy = profile?.[key];
10474
10690
  const workspacePolicy = candidate[key];
10475
10691
  return {
@@ -10600,6 +10816,10 @@ var init_workspaces = __esm(() => {
10600
10816
  });
10601
10817
  UNSUPPORTED_GLOB = /[?[\]{}]/;
10602
10818
  UNSUPPORTED_EXTGLOB = /[@+*]\(|!/;
10819
+ WorkspaceConfigError = class WorkspaceConfigError extends Error {
10820
+ name = "WorkspaceConfigError";
10821
+ };
10822
+ mainCheckouts = new Map;
10603
10823
  runtimeWorkspaceIndexSchema = object({
10604
10824
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
10605
10825
  }).passthrough();
@@ -11378,7 +11598,7 @@ var packageRoot = () => {
11378
11598
  var init_package_root = () => {};
11379
11599
 
11380
11600
  // packages/workit-core/src/core/store-lock.ts
11381
- import { spawnSync as spawnSync5 } from "node:child_process";
11601
+ import { spawnSync as spawnSync6 } from "node:child_process";
11382
11602
  import fs7 from "node:fs";
11383
11603
  import { hostname } from "node:os";
11384
11604
  var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, RECLAIM_GUARD_TTL_MS = 30000, defaultLockTimeoutMs = 250, defaultLockTimeout = () => defaultLockTimeoutMs, parseMetadataLock = (raw) => {
@@ -11413,7 +11633,7 @@ var metadataLockSchema, FOREIGN_LOCK_TTL_MS, UNREADABLE_LOCK_TTL_MS = 30000, REC
11413
11633
  }
11414
11634
  if (process.platform === "darwin" || process.platform === "freebsd") {
11415
11635
  try {
11416
- const run = spawnSync5("ps", ["-o", "lstart=", "-p", String(pid)], {
11636
+ const run = spawnSync6("ps", ["-o", "lstart=", "-p", String(pid)], {
11417
11637
  encoding: "utf8",
11418
11638
  timeout: 1000
11419
11639
  });
@@ -13507,14 +13727,16 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
13507
13727
  heldInProcess.delete(lock);
13508
13728
  }, envActor = () => {
13509
13729
  const value = (key) => process.env[key]?.trim() || null;
13730
+ const acting = hostSessionFromEnv(process.env);
13510
13731
  return {
13511
- host: value("WORKIT_HOST") ?? "runtime",
13512
- session: value("WORKIT_SESSION_ID"),
13732
+ host: acting.host ?? "runtime",
13733
+ session: acting.session,
13513
13734
  agentId: value("WORKIT_AGENT_ID")
13514
13735
  };
13515
13736
  };
13516
13737
  var init_task_store = __esm(() => {
13517
13738
  init_file_lock();
13739
+ init_host_session();
13518
13740
  init_package_root();
13519
13741
  init_task_contract();
13520
13742
  init_store_lock();
@@ -13535,7 +13757,7 @@ var init_workers = __esm(() => {
13535
13757
  });
13536
13758
 
13537
13759
  // packages/workit-core/src/check-config.ts
13538
- import { spawnSync as spawnSync6 } from "node:child_process";
13760
+ import { spawnSync as spawnSync7 } from "node:child_process";
13539
13761
  import fs10 from "node:fs";
13540
13762
  import path13 from "node:path";
13541
13763
  function splitCommand(command) {
@@ -13703,7 +13925,7 @@ function checkCommand(args, version, onPath = workitAvailable()) {
13703
13925
  return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
13704
13926
  }
13705
13927
  var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git3 = (cwd, args) => {
13706
- const run = spawnSync6("git", args, {
13928
+ const run = spawnSync7("git", args, {
13707
13929
  cwd,
13708
13930
  encoding: "utf8",
13709
13931
  timeout: 20000,
@@ -13738,7 +13960,7 @@ var init_check_config = __esm(() => {
13738
13960
  // packages/workit-core/src/core/task-evaluation.ts
13739
13961
  import { createHash as createHash8 } from "node:crypto";
13740
13962
  import * as fs11 from "node:fs";
13741
- import { spawnSync as spawnSync7 } from "node:child_process";
13963
+ import { spawnSync as spawnSync8 } from "node:child_process";
13742
13964
  import path14 from "node:path";
13743
13965
  function captureCandidate(root, scope, environment = []) {
13744
13966
  let checkout;
@@ -13816,7 +14038,7 @@ function captureCandidate(root, scope, environment = []) {
13816
14038
  const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
13817
14039
  if (values.some(({ value }) => value === null))
13818
14040
  completeness = "uncertain";
13819
- const headResult = spawnSync7("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
14041
+ const headResult = spawnSync8("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13820
14042
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
13821
14043
  completeness = "uncertain";
13822
14044
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -14189,7 +14411,7 @@ var digestBytes = (value) => createHash8("sha256").update(value).digest("hex"),
14189
14411
  }
14190
14412
  return left.length - right.length;
14191
14413
  }, gitPaths = (root) => {
14192
- const result = spawnSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
14414
+ const result = spawnSync8("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
14193
14415
  cwd: root,
14194
14416
  encoding: "buffer"
14195
14417
  });
@@ -14202,7 +14424,7 @@ var digestBytes = (value) => createHash8("sha256").update(value).digest("hex"),
14202
14424
  };
14203
14425
  }
14204
14426
  const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path14.sep).join("/")) : [];
14205
- const stagedDeleted = spawnSync7("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
14427
+ const stagedDeleted = spawnSync8("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
14206
14428
  if (stagedDeleted.status !== 0) {
14207
14429
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
14208
14430
  if (!/not a git repository/i.test(stderr))
@@ -16594,7 +16816,7 @@ var withReleaseTracks = (workspaceRoot, policy) => {
16594
16816
  error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
16595
16817
  };
16596
16818
  }
16597
- };
16819
+ }, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
16598
16820
  var init_branch = __esm(() => {
16599
16821
  init_branch_policy();
16600
16822
  init_commit_flavors();
@@ -16632,52 +16854,40 @@ var init_policy = __esm(() => {
16632
16854
  init_route_intent();
16633
16855
  });
16634
16856
 
16635
- // packages/workit-core/src/hooks/write-gate.ts
16636
- import { realpathSync as realpathSync5 } from "node:fs";
16637
- import path16 from "node:path";
16638
- function writeGate(cwd, paths) {
16639
- try {
16640
- const gate = gateFor(cwd);
16641
- if (!gate || gate.blockers.length === 0)
16642
- return NONE2;
16643
- if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
16644
- return NONE2;
16645
- const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
16646
- return {
16647
- kind: "deny",
16648
- reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
16649
- unblock: gate.blockers[0].reason
16650
- };
16651
- } catch {
16652
- return NONE2;
16653
- }
16654
- }
16655
- var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16656
- let rest = "";
16657
- for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
16658
- try {
16659
- return path16.join(realpathSync5(dir), rest);
16660
- } catch {
16661
- if (path16.dirname(dir) === dir)
16662
- return path16.resolve(file);
16663
- rest = rest ? path16.join(path16.basename(dir), rest) : path16.basename(dir);
16857
+ // packages/workit-core/src/hooks/shell-words.ts
16858
+ var closingQuote = (command, start, dialect) => {
16859
+ const quote = command[start];
16860
+ for (let index = start + 1;index < command.length; index++) {
16861
+ const char = command[index];
16862
+ if (quote === "'") {
16863
+ if (char !== "'")
16864
+ continue;
16865
+ if (dialect === "powershell" && command[index + 1] === "'") {
16866
+ index++;
16867
+ continue;
16868
+ }
16869
+ return index;
16870
+ }
16871
+ if (dialect === "posix" && char === "\\" || dialect === "powershell" && char === "`") {
16872
+ index++;
16873
+ continue;
16664
16874
  }
16875
+ if (char === '"')
16876
+ return index;
16665
16877
  }
16666
- }, exempt = (cwd, root, file, plans) => {
16667
- const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
16668
- const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
16669
- if (relative.startsWith("..") || path16.isAbsolute(relative))
16670
- return true;
16671
- const posix = relative.split(path16.sep).join("/");
16672
- const segments = posix.split("/");
16673
- return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
16674
- }, segmentsOf = (command) => {
16878
+ return -1;
16879
+ }, unquote = (body, quote, dialect) => {
16880
+ if (quote === "'")
16881
+ return dialect === "powershell" ? body.replaceAll("''", "'") : body;
16882
+ return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16883
+ }, segmentsOf = (command, dialect = "posix") => {
16675
16884
  const segments = [];
16676
16885
  let current = { words: [], redirects: [] };
16677
16886
  let word = "";
16678
16887
  let inWord = false;
16679
16888
  let pending = null;
16680
16889
  const heredocs = [];
16890
+ const escape = dialect === "posix" ? "\\" : "`";
16681
16891
  const flushWord = () => {
16682
16892
  if (!inWord)
16683
16893
  return;
@@ -16701,11 +16911,11 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16701
16911
  for (let index = 0;index < command.length; index++) {
16702
16912
  const char = command[index];
16703
16913
  if (char === "'" || char === '"') {
16704
- const end = command.indexOf(char, index + 1);
16705
- word += command.slice(index + 1, end < 0 ? undefined : end);
16914
+ const end = closingQuote(command, index, dialect);
16915
+ word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
16706
16916
  inWord = true;
16707
16917
  index = end < 0 ? command.length : end;
16708
- } else if (char === "\\" && index + 1 < command.length) {
16918
+ } else if (char === escape && index + 1 < command.length) {
16709
16919
  word += command[++index];
16710
16920
  inWord = true;
16711
16921
  } else if (char === `
@@ -16722,7 +16932,14 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16722
16932
  }
16723
16933
  } else if (/\s/.test(char))
16724
16934
  flushWord();
16725
- else if (";&|()".includes(char)) {
16935
+ else if (char === "(" || char === ")") {
16936
+ if (char === "(" && inWord && word.endsWith("$")) {
16937
+ word = word.slice(0, -1);
16938
+ inWord = word !== "";
16939
+ }
16940
+ flushSegment();
16941
+ segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
16942
+ } else if (";&|".includes(char)) {
16726
16943
  if (char === "&" && command[index + 1] === ">")
16727
16944
  continue;
16728
16945
  flushSegment();
@@ -16740,7 +16957,7 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16740
16957
  inWord = true;
16741
16958
  }
16742
16959
  pending = "redirect";
16743
- } else if (char === "<") {
16960
+ } else if (char === "<" && dialect === "posix") {
16744
16961
  flushWord();
16745
16962
  if (command[index + 1] === "<") {
16746
16963
  index++;
@@ -16756,6 +16973,47 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16756
16973
  }
16757
16974
  flushSegment();
16758
16975
  return segments;
16976
+ };
16977
+
16978
+ // packages/workit-core/src/hooks/write-gate.ts
16979
+ import { realpathSync as realpathSync5 } from "node:fs";
16980
+ import path16 from "node:path";
16981
+ function writeGate(cwd, paths) {
16982
+ try {
16983
+ const gate = gateFor(cwd);
16984
+ if (!gate || gate.blockers.length === 0)
16985
+ return NONE2;
16986
+ if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
16987
+ return NONE2;
16988
+ const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
16989
+ return {
16990
+ kind: "deny",
16991
+ reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
16992
+ unblock: gate.blockers[0].reason
16993
+ };
16994
+ } catch {
16995
+ return NONE2;
16996
+ }
16997
+ }
16998
+ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16999
+ let rest = "";
17000
+ for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
17001
+ try {
17002
+ return path16.join(realpathSync5(dir), rest);
17003
+ } catch {
17004
+ if (path16.dirname(dir) === dir)
17005
+ return path16.resolve(file);
17006
+ rest = rest ? path16.join(path16.basename(dir), rest) : path16.basename(dir);
17007
+ }
17008
+ }
17009
+ }, exempt = (cwd, root, file, plans) => {
17010
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
17011
+ const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
17012
+ if (relative.startsWith("..") || path16.isAbsolute(relative))
17013
+ return true;
17014
+ const posix = relative.split(path16.sep).join("/");
17015
+ const segments = posix.split("/");
17016
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
16759
17017
  }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
16760
17018
  let words = input;
16761
17019
  while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
@@ -16893,18 +17151,461 @@ var init_write_gate = __esm(() => {
16893
17151
  cache2 = new Map;
16894
17152
  });
16895
17153
 
17154
+ // packages/workit-core/src/hooks/raw-git.ts
17155
+ import { spawnSync as spawnSync9 } from "node:child_process";
17156
+ import { createHash as createHash10 } from "node:crypto";
17157
+ import fs12 from "node:fs";
17158
+ import os4 from "node:os";
17159
+ import path17 from "node:path";
17160
+ function rawInvocations(command, dialect = "posix", depth = 0) {
17161
+ const found = [];
17162
+ let dir = null;
17163
+ const groups = [];
17164
+ let stack = [];
17165
+ for (const segment of segmentsOf(command, dialect)) {
17166
+ if (segment.group === "open") {
17167
+ groups.push({ dir, stack: [...stack] });
17168
+ continue;
17169
+ }
17170
+ if (segment.group === "close") {
17171
+ const saved = groups.pop();
17172
+ if (saved)
17173
+ ({ dir, stack } = saved);
17174
+ continue;
17175
+ }
17176
+ const words = stripPrefixes(segment.words);
17177
+ const name = commandName(words[0]);
17178
+ if (CD.has(name)) {
17179
+ const target = cdTarget(words);
17180
+ dir = target === null ? os4.homedir() : joinDir(dir, target);
17181
+ continue;
17182
+ }
17183
+ if (PUSHD.has(name)) {
17184
+ const target = cdTarget(words);
17185
+ stack.push(dir);
17186
+ dir = target === null || target === "-" ? undefined : joinDir(dir, target);
17187
+ continue;
17188
+ }
17189
+ if (POPD.has(name)) {
17190
+ dir = stack.length ? stack.pop() : undefined;
17191
+ continue;
17192
+ }
17193
+ if (SHELLS.has(name) && depth < 3) {
17194
+ const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
17195
+ const script = flag > 0 ? words[flag + 1] : undefined;
17196
+ if (script)
17197
+ for (const inner of rawInvocations(script, "posix", depth + 1))
17198
+ found.push({
17199
+ ...inner,
17200
+ dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
17201
+ });
17202
+ continue;
17203
+ }
17204
+ if (name === "gh" || name === "glab") {
17205
+ found.push({ tool: name, dir, args: words.slice(1) });
17206
+ continue;
17207
+ }
17208
+ if (name !== "git")
17209
+ continue;
17210
+ let here = dir;
17211
+ let index = 1;
17212
+ for (;index < words.length; index++) {
17213
+ const word = words[index];
17214
+ if (word === "-C")
17215
+ here = words[index + 1] ? joinDir(here, words[++index]) : here;
17216
+ else if (GIT_VALUE_OPTIONS.has(word))
17217
+ index++;
17218
+ else if (!word.startsWith("-"))
17219
+ break;
17220
+ }
17221
+ found.push({ tool: "git", dir: here, args: words.slice(index) });
17222
+ }
17223
+ return found;
17224
+ }
17225
+ function rawAction(invocation) {
17226
+ const args = invocation.args;
17227
+ if (asksHelp(invocation))
17228
+ return null;
17229
+ const [first, second] = args;
17230
+ if (invocation.tool === "git") {
17231
+ if (first === "commit")
17232
+ return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
17233
+ if (first === "push")
17234
+ return pushAction(args);
17235
+ return null;
17236
+ }
17237
+ const forge = invocation.tool;
17238
+ const noun = forge === "gh" ? "pr" : "mr";
17239
+ if (first !== noun)
17240
+ return null;
17241
+ if (second === "merge")
17242
+ return { kind: "merge", forge };
17243
+ if (second === "create")
17244
+ return { kind: "pr-create", forge };
17245
+ if (["view", "checks", "status", "ci", "list"].includes(second ?? ""))
17246
+ return { kind: "pr-read", forge };
17247
+ return null;
17248
+ }
17249
+ function rawGitPre(input, command) {
17250
+ try {
17251
+ if (!mentionsRawTool(command))
17252
+ return NONE3;
17253
+ const nudges = [];
17254
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
17255
+ if (action.kind === "merge")
17256
+ return deny(refusal(action, null));
17257
+ if (action.kind === "push")
17258
+ for (const target of action.targets) {
17259
+ const branch = target ?? repo.branch;
17260
+ if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
17261
+ return deny(refusal(action, branch));
17262
+ }
17263
+ const nudge = nudgeFor(action, input.host, input.session.id);
17264
+ if (nudge && !nudges.includes(nudge))
17265
+ nudges.push(nudge);
17266
+ }
17267
+ return nudges.length ? { kind: "context", text: nudges.join(`
17268
+ `) } : NONE3;
17269
+ } catch {
17270
+ return NONE3;
17271
+ }
17272
+ }
17273
+ function noteRawCommit(input, command, key) {
17274
+ try {
17275
+ if (!input.session.id || !/\bcommit\b/.test(command))
17276
+ return;
17277
+ const at = Math.floor(Date.now() / 1000);
17278
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
17279
+ if (action.kind !== "commit")
17280
+ continue;
17281
+ const file = markerFile(repo, input.session.id, key);
17282
+ fs12.mkdirSync(path17.dirname(file), { recursive: true });
17283
+ fs12.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
17284
+ `);
17285
+ }
17286
+ } catch {}
17287
+ }
17288
+ function rawGitPost(input, command, key) {
17289
+ try {
17290
+ if (!input.session.id || !/\bcommit\b/.test(command))
17291
+ return;
17292
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input)))
17293
+ if (action.kind === "commit")
17294
+ settle2(input, repo, markerFile(repo, input.session.id, key));
17295
+ } catch {}
17296
+ }
17297
+ function settlePendingCommit(input) {
17298
+ try {
17299
+ if (!input.session.id)
17300
+ return;
17301
+ const repo = repoAt(input.cwd);
17302
+ if (!repo)
17303
+ return;
17304
+ const file = markerFile(repo, input.session.id, NEXT_COMMAND);
17305
+ if (fs12.existsSync(file))
17306
+ settle2(input, repo, file);
17307
+ } catch {}
17308
+ }
17309
+ var NONE3, SHELLS, PREFIXES3, SCRIPT_FLAG, CD, PUSHD, POPD, GIT_VALUE_OPTIONS, isAbsolute = (value) => path17.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\"), joinDir = (base, next) => {
17310
+ if (base === undefined || /[$`*?]/.test(next) || next === "-")
17311
+ return;
17312
+ const expanded = next === "~" || next.startsWith("~/") ? path17.join(os4.homedir(), next.slice(1)) : next;
17313
+ return base === null || isAbsolute(expanded) ? expanded : path17.join(base, expanded);
17314
+ }, cdTarget = (words) => {
17315
+ const args = words.slice(1);
17316
+ const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
17317
+ if (flag >= 0)
17318
+ return args[flag + 1] ?? null;
17319
+ return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
17320
+ }, stripPrefixes = (input) => {
17321
+ let words = input;
17322
+ for (;; ) {
17323
+ const first = words[0];
17324
+ if (first === undefined)
17325
+ return words;
17326
+ if (/^\w+=/.test(first))
17327
+ words = words.slice(1);
17328
+ else if (PREFIXES3.has(first)) {
17329
+ words = words.slice(1);
17330
+ while (words[0]?.startsWith("-"))
17331
+ words = words.slice(words[0] === "-u" ? 2 : 1);
17332
+ } else if (first === "timeout") {
17333
+ words = words.slice(1);
17334
+ while (words[0]?.startsWith("-"))
17335
+ words = words.slice(1);
17336
+ words = words.slice(1);
17337
+ } else
17338
+ return words;
17339
+ }
17340
+ }, commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, ""), PUSH_VALUE_OPTIONS, pushAction = (args) => {
17341
+ let force = false;
17342
+ let lease = false;
17343
+ let wholesale = false;
17344
+ let remote = null;
17345
+ const positionals = [];
17346
+ for (let index = 1;index < args.length; index++) {
17347
+ const arg = args[index];
17348
+ if (arg === "--") {
17349
+ positionals.push(...args.slice(index + 1));
17350
+ break;
17351
+ }
17352
+ if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
17353
+ return null;
17354
+ if (arg === "--repo")
17355
+ remote = args[++index] ?? null;
17356
+ else if (arg.startsWith("--repo="))
17357
+ remote = arg.slice("--repo=".length);
17358
+ else if (PUSH_VALUE_OPTIONS.has(arg))
17359
+ index++;
17360
+ else if (arg === "--force" || /^-[a-zA-Z]*f[a-zA-Z]*$/.test(arg))
17361
+ force = true;
17362
+ else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
17363
+ lease = true;
17364
+ else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
17365
+ wholesale = true;
17366
+ else if (!arg.startsWith("-"))
17367
+ positionals.push(arg);
17368
+ }
17369
+ const specs = remote === null ? positionals.slice(1) : positionals;
17370
+ remote ??= positionals[0] ?? null;
17371
+ const targets = [];
17372
+ for (const raw of specs) {
17373
+ if (raw.startsWith("+"))
17374
+ force = true;
17375
+ const spec = raw.replace(/^\+/, "");
17376
+ const colon = spec.indexOf(":");
17377
+ const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
17378
+ if (!destination)
17379
+ continue;
17380
+ if (destination === "HEAD")
17381
+ targets.push(null);
17382
+ else if (destination.startsWith("refs/heads/"))
17383
+ targets.push(destination.slice(11));
17384
+ else if (!destination.startsWith("refs/"))
17385
+ targets.push(destination);
17386
+ }
17387
+ if (specs.length === 0 && !wholesale)
17388
+ targets.push(null);
17389
+ return { kind: "push", targets, blindForce: force && !lease, remote };
17390
+ }, VALUE_OPTIONS, asksHelp = (invocation) => {
17391
+ const { tool, args } = invocation;
17392
+ if (args[0] === "help" || tool !== "git" && args[1] === "help")
17393
+ return true;
17394
+ const values = VALUE_OPTIONS[tool];
17395
+ for (let index = 0;index < args.length; index++) {
17396
+ const arg = args[index];
17397
+ if (arg === "--")
17398
+ return false;
17399
+ if (arg === "--help" || arg === "-h")
17400
+ return true;
17401
+ if (values.has(arg))
17402
+ index++;
17403
+ }
17404
+ return false;
17405
+ }, repoAt = (dir) => {
17406
+ const location = resolveStore(dir);
17407
+ if (location instanceof Error || !location.shared)
17408
+ return null;
17409
+ return { location, dir, branch: resolveTaskKey(dir, location).branch };
17410
+ }, isWorkitWorkspace = (repo) => {
17411
+ if (fs12.existsSync(repo.location.dir))
17412
+ return true;
17413
+ try {
17414
+ return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
17415
+ } catch {
17416
+ return false;
17417
+ }
17418
+ }, guardedBranch = (dir, branch) => {
17419
+ try {
17420
+ const policy = resolveBranchPolicyFor(dir);
17421
+ return policy.protected.has(branch.toLowerCase()) || policy.defaultTargetBranch.toLowerCase() === branch.toLowerCase();
17422
+ } catch {
17423
+ return false;
17424
+ }
17425
+ }, toPushRemote = (repo, remote) => {
17426
+ if (remote === null)
17427
+ return true;
17428
+ try {
17429
+ const pushRemote = pushRemoteName(repo.dir, repo.branch);
17430
+ return pushRemote === null || pushRemote === remote;
17431
+ } catch {
17432
+ return false;
17433
+ }
17434
+ }, headSha2 = (repo) => {
17435
+ const git = repo.location.git;
17436
+ if (!git || git.reftable)
17437
+ return null;
17438
+ try {
17439
+ const head = fs12.readFileSync(path17.join(git.gitDir, "HEAD"), "utf8").trim();
17440
+ if (/^[0-9a-f]{40,64}$/.test(head))
17441
+ return head;
17442
+ const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
17443
+ if (!ref)
17444
+ return null;
17445
+ const common = path17.dirname(repo.location.dir);
17446
+ try {
17447
+ return fs12.readFileSync(path17.join(common, ref), "utf8").trim() || null;
17448
+ } catch {}
17449
+ const packed = fs12.readFileSync(path17.join(common, "packed-refs"), "utf8");
17450
+ return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
17451
+ } catch {
17452
+ return null;
17453
+ }
17454
+ }, quote = (text) => `\`${text}\``, refusal = (action, branch) => {
17455
+ if (action.kind === "merge")
17456
+ return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
17457
+ return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
17458
+ }, nudgeFor = (action, host, session) => {
17459
+ switch (action.kind) {
17460
+ case "commit": {
17461
+ const prefix = host === "cursor" && session ? `WORKIT_SESSION_ID=${session} ` : "";
17462
+ return action.amend ? `workit: raw ${quote("git commit --amend")} is recorded for this session; for new commits prefer ${quote(`${prefix}workit git commit -m '<msg>'`)} (adds the Workit-Session trailer and checks branch and commit policy).` : `workit: prefer ${quote(`${prefix}workit git commit -m '<msg>' [--all | -- <paths>]`)}: it adds the Workit-Session trailer and checks branch and commit policy.`;
17463
+ }
17464
+ case "push":
17465
+ return action.blindForce ? `workit: ${quote("git push --force")} overwrites without a lease; use ${quote("workit git push --force-with-lease")}.` : `workit: prefer ${quote("workit git push")}: it refuses protected branches and verifies the remote tip.`;
17466
+ case "pr-create":
17467
+ return `workit: prefer ${quote("workit pr create --fill")} (or --title/--body): it records the PR the merge gate checks.`;
17468
+ case "pr-read":
17469
+ return `workit: ${quote("workit pr status")} shows the PR, its checks and the verdict state; ${quote("workit ci wait")} waits for checks.`;
17470
+ case "merge":
17471
+ return null;
17472
+ }
17473
+ }, actionsIn = (cwd, command, dialect) => {
17474
+ const out = [];
17475
+ const repos = new Map;
17476
+ for (const invocation of rawInvocations(command, dialect)) {
17477
+ if (invocation.dir === undefined)
17478
+ continue;
17479
+ const action = rawAction(invocation);
17480
+ if (!action)
17481
+ continue;
17482
+ const dir = path17.resolve(cwd, invocation.dir ?? ".");
17483
+ if (!repos.has(dir)) {
17484
+ const repo = repoAt(dir);
17485
+ repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
17486
+ }
17487
+ const repo = repos.get(dir);
17488
+ if (repo)
17489
+ out.push({ action, repo });
17490
+ }
17491
+ return out;
17492
+ }, mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command), dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix", deny = (reason) => ({ kind: "deny", reason, unblock: null }), NEXT_COMMAND = "next", MARKER_TTL_SECONDS = 3600, markerFile = (repo, session, key) => path17.join(repo.location.dir, "hooks", `commit-${createHash10("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`), callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash10("sha256").update(command).digest("hex")}`, headCommit = (dir) => {
17493
+ const run = spawnSync9("git", [
17494
+ "log",
17495
+ "-1",
17496
+ "--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
17497
+ ], {
17498
+ cwd: dir,
17499
+ encoding: "utf8",
17500
+ timeout: 5000,
17501
+ windowsHide: true,
17502
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
17503
+ });
17504
+ const [sha, committed, subject = "", trailer = ""] = (run.status === 0 ? run.stdout.trim() : "").split("\x1F");
17505
+ return sha && /^[0-9a-f]{40,64}$/.test(sha) ? { sha, committed: Number(committed), subject, trailer: trailer.trim() || null } : null;
17506
+ }, settle2 = (input, repo, file) => {
17507
+ let marker;
17508
+ try {
17509
+ marker = JSON.parse(fs12.readFileSync(file, "utf8"));
17510
+ } catch {
17511
+ return;
17512
+ }
17513
+ fs12.rmSync(file, { force: true });
17514
+ const session = input.session.id;
17515
+ const now = Date.now() / 1000;
17516
+ if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
17517
+ return;
17518
+ if (!session || !repo.branch)
17519
+ return;
17520
+ const head = headCommit(repo.dir);
17521
+ if (!head || head.sha === marker.head || head.committed < marker.at)
17522
+ return;
17523
+ if (head.trailer && head.trailer !== session)
17524
+ return;
17525
+ const ledger = readLedger(repo.dir);
17526
+ if (!ledger.ok)
17527
+ return;
17528
+ const claimed = ledger.value.rows.some((row) => row.type === "commit.recorded" && (row.sha === head.sha || row.head === head.sha));
17529
+ if (claimed)
17530
+ return;
17531
+ appendHookObserved(repo.dir, {
17532
+ type: "commit.recorded",
17533
+ actor: { host: input.host, session, agentId: input.session.agentId },
17534
+ branch: repo.branch,
17535
+ head: head.sha,
17536
+ sha: head.sha,
17537
+ session,
17538
+ agentId: input.session.agentId,
17539
+ subject: head.subject.slice(0, 200),
17540
+ files: [],
17541
+ fileCount: null,
17542
+ via: "raw_shell"
17543
+ });
17544
+ };
17545
+ var init_raw_git = __esm(() => {
17546
+ init_branch();
17547
+ init_workspaces();
17548
+ init_rev();
17549
+ init_ledger();
17550
+ init_paths();
17551
+ NONE3 = { kind: "none" };
17552
+ SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
17553
+ PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
17554
+ SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
17555
+ CD = new Set(["cd", "chdir", "set-location", "sl"]);
17556
+ PUSHD = new Set(["pushd", "push-location"]);
17557
+ POPD = new Set(["popd", "pop-location"]);
17558
+ GIT_VALUE_OPTIONS = new Set([
17559
+ "-c",
17560
+ "--git-dir",
17561
+ "--work-tree",
17562
+ "--namespace",
17563
+ "--exec-path",
17564
+ "--config-env",
17565
+ "--super-prefix"
17566
+ ]);
17567
+ PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17568
+ VALUE_OPTIONS = {
17569
+ git: new Set([
17570
+ "-m",
17571
+ "--message",
17572
+ "-F",
17573
+ "--file",
17574
+ "-C",
17575
+ "-c",
17576
+ "--reuse-message",
17577
+ "--reedit-message",
17578
+ "--author",
17579
+ "--date",
17580
+ "--trailer",
17581
+ "-t",
17582
+ "--template",
17583
+ "--cleanup",
17584
+ "--fixup",
17585
+ "--squash",
17586
+ "-o",
17587
+ "--push-option",
17588
+ "--repo",
17589
+ "--receive-pack",
17590
+ "--exec"
17591
+ ]),
17592
+ gh: new Set(["-b", "--body", "-F", "--body-file", "-t", "--title", "--subject", "-A", "--author-email"].concat(["-B", "--base", "-H", "--head", "-l", "--label", "-a", "--assignee"]).concat(["-r", "--reviewer", "-m", "--milestone", "-p", "--project", "-T", "--template"]).concat(["--match-head-commit", "-R", "--repo"])),
17593
+ glab: new Set(["-m", "--message", "--squash-message", "--sha", "-t", "--title", "-d", "--description"].concat(["-b", "--target-branch", "-s", "--source-branch", "-l", "--label"]).concat(["-a", "--assignee", "--reviewer", "-R", "--repo"]))
17594
+ };
17595
+ });
17596
+
16896
17597
  // packages/workit-core/src/hooks/hosts/fields.ts
16897
17598
  import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
16898
- import path17 from "node:path";
17599
+ import path18 from "node:path";
16899
17600
  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) => {
16900
- if (!nonEmpty2(value) || !path17.isAbsolute(value) || !existsSync6(value))
17601
+ if (!nonEmpty2(value) || !path18.isAbsolute(value) || !existsSync6(value))
16901
17602
  return null;
16902
17603
  try {
16903
17604
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
16904
17605
  } catch {
16905
17606
  return null;
16906
17607
  }
16907
- }, SHELLS, SCRIPT_FLAG, quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg), commandText = (value) => {
17608
+ }, SHELLS2, SCRIPT_FLAG2, quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg), commandText = (value) => {
16908
17609
  if (nonEmpty2(value))
16909
17610
  return value;
16910
17611
  if (!Array.isArray(value) || value.length === 0)
@@ -16913,9 +17614,9 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
16913
17614
  if (!argv.every((arg) => typeof arg === "string"))
16914
17615
  return null;
16915
17616
  const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
16916
- if (SHELLS.has(shell))
17617
+ if (SHELLS2.has(shell))
16917
17618
  for (let index = 1;index < argv.length - 1; index++) {
16918
- if (SCRIPT_FLAG.test(argv[index]))
17619
+ if (SCRIPT_FLAG2.test(argv[index]))
16919
17620
  return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
16920
17621
  if (!/^[-/]/.test(argv[index]))
16921
17622
  break;
@@ -16933,8 +17634,8 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
16933
17634
  return [...new Set(paths)];
16934
17635
  };
16935
17636
  var init_fields = __esm(() => {
16936
- SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
16937
- SCRIPT_FLAG = /^(?:-\w*c|-command|\/c)$/i;
17637
+ SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
17638
+ SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
16938
17639
  WRITE_TOOLS = new Set([
16939
17640
  "write",
16940
17641
  "edit",
@@ -16964,7 +17665,7 @@ function handleHook(input, deps) {
16964
17665
  switch (event.kind) {
16965
17666
  case "session.start": {
16966
17667
  if (!usable2(descriptor.context.sessionStart))
16967
- return NONE3;
17668
+ return NONE4;
16968
17669
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
16969
17670
  const offer = event.source === "startup" && (key === null || !offered.has(key));
16970
17671
  if (offer && key !== null)
@@ -16979,19 +17680,34 @@ function handleHook(input, deps) {
16979
17680
  }
16980
17681
  case "context.turn": {
16981
17682
  if (!usable2(descriptor.context.perTurn))
16982
- return NONE3;
17683
+ return NONE4;
16983
17684
  const text = turnContextText(input, descriptor);
16984
- return text ? { kind: "context", text } : NONE3;
17685
+ return text ? { kind: "context", text } : NONE4;
16985
17686
  }
16986
17687
  case "shell.pre": {
16987
- const policy = usable2(descriptor.shellPolicy.deny) ? shellPolicy(input.cwd, event.command) : NONE3;
16988
- if (policy.kind !== "none" || !usable2(descriptor.events["write.pre"].support))
17688
+ const canDeny = usable2(descriptor.shellPolicy.deny);
17689
+ const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
17690
+ if (policy.kind !== "none")
16989
17691
  return policy;
16990
- const writes = shellWrites(event.command);
16991
- return writes.writes ? writeGate(input.cwd, writes.targets) : NONE3;
17692
+ const postTool = usable2(descriptor.events["shell.post"].support);
17693
+ if (!postTool)
17694
+ settlePendingCommit(input);
17695
+ const raw = canDeny ? rawGitPre(input, event.command) : NONE4;
17696
+ if (raw.kind === "deny")
17697
+ return raw;
17698
+ const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
17699
+ const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE4;
17700
+ if (gate.kind === "deny")
17701
+ return gate;
17702
+ noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
17703
+ return raw;
16992
17704
  }
17705
+ case "shell.post":
17706
+ if (usable2(descriptor.events["shell.post"].support))
17707
+ rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
17708
+ return NONE4;
16993
17709
  case "write.pre":
16994
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE3;
17710
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE4;
16995
17711
  case "subagent.start":
16996
17712
  return {
16997
17713
  kind: "context",
@@ -17003,14 +17719,13 @@ function handleHook(input, deps) {
17003
17719
  userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
17004
17720
  };
17005
17721
  case "tool.pre":
17006
- case "shell.post":
17007
17722
  case "subagent.stop":
17008
17723
  case "prompt.submit":
17009
17724
  case "stop":
17010
- return NONE3;
17725
+ return NONE4;
17011
17726
  }
17012
17727
  }
17013
- var usable2 = (value) => value === "native" || value === "partial", NONE3, offered, CLAUDE_WORKTREE_IMPLEMENTER = "workit:implementer", CLAUDE_JUDGES, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), subagentStartText = (host, session, descriptor, event) => {
17728
+ var usable2 = (value) => value === "native" || value === "partial", NONE4, offered, CLAUDE_WORKTREE_IMPLEMENTER = "workit:implementer", CLAUDE_JUDGES, subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128), subagentStartText = (host, session, descriptor, event) => {
17014
17729
  if (host === "claude_code" && event.agentType === CLAUDE_WORKTREE_IMPLEMENTER)
17015
17730
  return `Workit observed ${descriptor.label} subagent ${event.agentId} (${event.agentType}) working in its own git worktree: it may edit and commit there, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (e.g. feature/<slug>); branch policy hooks still deny protected or non-compliant branches. Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
17016
17731
  const readOnly = `Workit observed ${descriptor.label} subagent ${event.agentId} (${event.agentType}) as read-only/agent-guided.`;
@@ -17019,10 +17734,10 @@ var usable2 = (value) => value === "native" || value === "partial", NONE3, offer
17019
17734
  return readOnly;
17020
17735
  }, failureDecision = (descriptor, event, error) => {
17021
17736
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
17022
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE3;
17737
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE4;
17023
17738
  if (event === "session.start" || event === "subagent.start")
17024
17739
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
17025
- return NONE3;
17740
+ return NONE4;
17026
17741
  }, dispatchHook = (adapter, raw, env = process.env) => {
17027
17742
  const parsed = adapter.parse(raw, env);
17028
17743
  if (!parsed.ok)
@@ -17048,14 +17763,15 @@ var usable2 = (value) => value === "native" || value === "partial", NONE3, offer
17048
17763
  var init_handle = __esm(() => {
17049
17764
  init_context();
17050
17765
  init_policy();
17766
+ init_raw_git();
17051
17767
  init_write_gate();
17052
- NONE3 = { kind: "none" };
17768
+ NONE4 = { kind: "none" };
17053
17769
  offered = new Set;
17054
17770
  CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
17055
17771
  });
17056
17772
 
17057
17773
  // packages/workit-core/src/hooks/hosts/claude-code.ts
17058
- var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
17774
+ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix", toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
17059
17775
  const toolUseId = optionalText(value.tool_use_id);
17060
17776
  switch (name) {
17061
17777
  case "SessionStart":
@@ -17078,7 +17794,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
17078
17794
  if (!SHELL_TOOLS.has(value.tool_name))
17079
17795
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
17080
17796
  const command = toolCommand(value);
17081
- return command ? { ok: true, event: { kind: "shell.pre", command, toolUseId } } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
17797
+ return command ? { ok: true, event: { kind: "shell.pre", command, toolUseId, dialect: dialectOf2(value) } } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
17082
17798
  }
17083
17799
  case "PostToolUse": {
17084
17800
  const command = toolCommand(value);
@@ -17092,7 +17808,8 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
17092
17808
  command,
17093
17809
  stdout: optionalText(response.stdout) ?? "",
17094
17810
  exitCode: typeof response.exit_code === "number" ? response.exit_code : null,
17095
- toolUseId
17811
+ toolUseId,
17812
+ dialect: dialectOf2(value)
17096
17813
  }
17097
17814
  };
17098
17815
  }
@@ -17255,7 +17972,13 @@ var init_claude_code = __esm(() => {
17255
17972
  };
17256
17973
  SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
17257
17974
  SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
17258
- CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
17975
+ CONTEXT_EVENTS = new Set([
17976
+ "SessionStart",
17977
+ "UserPromptSubmit",
17978
+ "SubagentStart",
17979
+ "PreToolUse",
17980
+ "PostToolUse"
17981
+ ]);
17259
17982
  claudeCodeAdapter = {
17260
17983
  descriptor: CLAUDE_CODE_DESCRIPTOR,
17261
17984
  parse(raw) {
@@ -17309,10 +18032,11 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17309
18032
  const cwd = existingDirectory(value.cwd);
17310
18033
  if (!cwd)
17311
18034
  return { ok: false, error: "cwd must be an existing absolute directory" };
17312
- if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
18035
+ const toolEvent = event === "PreToolUse" || event === "PostToolUse";
18036
+ if (toolEvent && !nonEmpty2(value.tool_name))
17313
18037
  return { ok: false, error: "tool_name is required" };
17314
18038
  const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
17315
- if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
18039
+ if (toolEvent && isShellTool(value.tool_name) && !command)
17316
18040
  return { ok: false, error: "tool_input.command is required for shell tools" };
17317
18041
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
17318
18042
  return { ok: false, error: "agent_id is required" };
@@ -17328,7 +18052,8 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17328
18052
  ...event === "SessionStart" ? { source: SOURCES2.has(String(value.source)) ? value.source : "resume" } : {},
17329
18053
  ...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
17330
18054
  ...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},
17331
- ...event === "PreToolUse" ? { tool_input: value.tool_input } : {},
18055
+ ...toolEvent ? { tool_input: value.tool_input } : {},
18056
+ ...event === "PostToolUse" && typeof value.tool_response === "string" ? { tool_response: value.tool_response } : {},
17332
18057
  ...command ? { command } : {},
17333
18058
  ...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
17334
18059
  ...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
@@ -17352,6 +18077,16 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17352
18077
  toolUseId
17353
18078
  } : { kind: "tool.pre", tool: input.tool_name, toolUseId };
17354
18079
  }
18080
+ case "PostToolUse": {
18081
+ const toolUseId = input.tool_use_id ?? null;
18082
+ return isShellTool(input.tool_name) ? {
18083
+ kind: "shell.post",
18084
+ command: input.command ?? "",
18085
+ stdout: input.tool_response ?? "",
18086
+ exitCode: null,
18087
+ toolUseId
18088
+ } : { kind: "tool.pre", tool: input.tool_name, toolUseId };
18089
+ }
17355
18090
  case "SubagentStart":
17356
18091
  return {
17357
18092
  kind: "subagent.start",
@@ -17400,7 +18135,7 @@ var init_codex = __esm(() => {
17400
18135
  "shell.pre": { support: "native", native: "PreToolUse" },
17401
18136
  "tool.pre": { support: "native", native: "PreToolUse" },
17402
18137
  "write.pre": { support: "undocumented", native: null },
17403
- "shell.post": undocumented,
18138
+ "shell.post": { support: "native", native: "PostToolUse" },
17404
18139
  "subagent.start": { support: "native", native: "SubagentStart" },
17405
18140
  "subagent.stop": { support: "native", native: "SubagentStop" },
17406
18141
  "prompt.submit": undocumented,
@@ -17421,7 +18156,7 @@ var init_codex = __esm(() => {
17421
18156
  worktreeIsolation: "undocumented",
17422
18157
  maxConcurrency: "undocumented"
17423
18158
  },
17424
- provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
18159
+ provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
17425
18160
  interaction: { questions: "none", writeBoundary: "partial" },
17426
18161
  stopControl: "undocumented",
17427
18162
  shellAvailable: "native",
@@ -17487,6 +18222,7 @@ var init_codex = __esm(() => {
17487
18222
  EVENTS2 = {
17488
18223
  SessionStart: "session.start",
17489
18224
  PreToolUse: "shell.pre",
18225
+ PostToolUse: "shell.post",
17490
18226
  SubagentStart: "subagent.start",
17491
18227
  SubagentStop: "subagent.stop"
17492
18228
  };
@@ -17521,12 +18257,12 @@ var init_codex = __esm(() => {
17521
18257
  };
17522
18258
  },
17523
18259
  render: render2,
17524
- addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. A verifier or reviewer records workit ledger verdict under a session the lead assigns (WORKIT_SESSION_ID=<lead>-v<n>), never the author's. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
18260
+ addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under a session the lead assigns (WORKIT_HOST=codex_cli WORKIT_SESSION_ID=<lead>-v<n>), never the author's. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
17525
18261
  };
17526
18262
  });
17527
18263
 
17528
18264
  // packages/workit-core/src/hooks/hosts/cursor.ts
17529
- import path18 from "node:path";
18265
+ import path19 from "node:path";
17530
18266
  var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
17531
18267
  if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
17532
18268
  return { ok: false, error: "hook_event_name is required" };
@@ -17595,7 +18331,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
17595
18331
  case "preCompact":
17596
18332
  return { kind: "compact.pre", trigger: "auto" };
17597
18333
  }
17598
- }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path18.sep) ? root : `${root}${path18.sep}`), hookCwd = (input) => {
18334
+ }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path19.sep) ? root : `${root}${path19.sep}`), hookCwd = (input) => {
17599
18335
  const cwd = existingDirectory(input.cwd);
17600
18336
  if (cwd && input.hook_event_name === "beforeShellExecution")
17601
18337
  return cwd;
@@ -17613,6 +18349,8 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
17613
18349
  json: cursorDeny(decision.reason),
17614
18350
  exitCode: native !== null && BLOCKING.has(native) ? 2 : 0
17615
18351
  };
18352
+ if (decision.kind === "context" && native === "beforeShellExecution")
18353
+ return { json: { permission: "allow", agent_message: decision.text }, exitCode: 0 };
17616
18354
  if (decision.kind === "context")
17617
18355
  return { json: { additional_context: decision.text }, exitCode: 0 };
17618
18356
  if (decision.kind === "notice")
@@ -17762,7 +18500,8 @@ var init_cursor = __esm(() => {
17762
18500
  }
17763
18501
  };
17764
18502
  },
17765
- render: render3
18503
+ render: render3,
18504
+ addendum: (input) => input.session.id ? `<workit-session>Cursor puts no session id in the shell: prefix workit commands that record authorship with WORKIT_SESSION_ID=${input.session.id} (e.g. \`WORKIT_SESSION_ID=${input.session.id} workit git commit -m …\`). A verifier uses its own id, never this one.</workit-session>` : null
17766
18505
  };
17767
18506
  });
17768
18507
 
@@ -17812,6 +18551,7 @@ var init_hooks = __esm(async () => {
17812
18551
  init_context();
17813
18552
  init_policy();
17814
18553
  init_write_gate();
18554
+ init_raw_git();
17815
18555
  init_fields();
17816
18556
  init_handle();
17817
18557
  init_claude_code();