@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.
@@ -8527,9 +8527,28 @@ function patchId(cwd, base, head) {
8527
8527
  const id = git(cwd, ["patch-id", "--stable"], { input: diff.stdout });
8528
8528
  return line(id)?.split(/\s+/u)[0] ?? null;
8529
8529
  }
8530
+ function remoteNames(cwd) {
8531
+ return (git(cwd, ["remote"]).stdout ?? "").split(/\r?\n/u).map((value) => value.trim()).filter(Boolean);
8532
+ }
8533
+ var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
8530
8534
  function currentBranch(cwd) {
8531
8535
  return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
8532
8536
  }
8537
+ function pushRemoteName(cwd, branch) {
8538
+ const name = branch ?? currentBranch(cwd);
8539
+ const remotes = remoteNames(cwd);
8540
+ const candidates = [
8541
+ name ? config2(cwd, `branch.${name}.pushRemote`) : null,
8542
+ config2(cwd, "remote.pushDefault"),
8543
+ name ? config2(cwd, `branch.${name}.remote`) : null
8544
+ ];
8545
+ for (const candidate of candidates)
8546
+ if (candidate && candidate !== "." && remotes.includes(candidate))
8547
+ return candidate;
8548
+ if (remotes.includes("origin"))
8549
+ return "origin";
8550
+ return remotes.length === 1 ? remotes[0] : null;
8551
+ }
8533
8552
 
8534
8553
  // packages/workit-core/src/ledger.ts
8535
8554
  import { spawnSync as spawnSync3 } from "node:child_process";
@@ -8537,6 +8556,35 @@ import { createHash as createHash4, randomBytes } from "node:crypto";
8537
8556
  import fs3 from "node:fs";
8538
8557
  import path3 from "node:path";
8539
8558
 
8559
+ // packages/workit-core/src/host-session.ts
8560
+ var HOST_SESSION_VARS = [
8561
+ ["OPENCODE_SESSION_ID", () => "opencode"],
8562
+ ["PI_SESSION_ID", () => "pi"],
8563
+ [
8564
+ "CODEX_THREAD_ID",
8565
+ (env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
8566
+ ]
8567
+ ];
8568
+ var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
8569
+ function hostSessionFromEnv(env) {
8570
+ const value = (key) => env[key]?.trim() || null;
8571
+ let inner = null;
8572
+ for (const [key, host] of HOST_SESSION_VARS) {
8573
+ const session = value(key);
8574
+ if (session) {
8575
+ inner = { host: host(env), session };
8576
+ break;
8577
+ }
8578
+ }
8579
+ const declared = value("WORKIT_HOST");
8580
+ if (env.WORKIT_SESSION_ID !== undefined) {
8581
+ const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
8582
+ if (!inherited)
8583
+ return { host: declared, session: value("WORKIT_SESSION_ID") };
8584
+ }
8585
+ return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
8586
+ }
8587
+
8540
8588
  // packages/workit-core/src/store/paths.ts
8541
8589
  import { spawnSync as spawnSync2 } from "node:child_process";
8542
8590
  import { createHash as createHash3 } from "node:crypto";
@@ -8819,6 +8867,7 @@ var checkoutSlug = (root) => {
8819
8867
 
8820
8868
  // packages/workit-core/src/ledger.ts
8821
8869
  var LEDGER_VERSION = 1;
8870
+ var MAX_LINE_BYTES = 4096;
8822
8871
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
8823
8872
  var FAILING = new Set(["failed", "blocked"]);
8824
8873
  var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
@@ -8918,6 +8967,7 @@ function ledgerPath(cwd) {
8918
8967
  const root = storeRoot(cwd);
8919
8968
  return root.ok ? { ok: true, value: path3.join(root.value.root, "ledger", "ledger.jsonl") } : root;
8920
8969
  }
8970
+ var newId2 = () => `${Date.now().toString(36)}-${randomBytes(6).toString("hex")}`;
8921
8971
  var NETWORK_FS_TYPES = new Set([
8922
8972
  26985,
8923
8973
  20859,
@@ -8930,6 +8980,158 @@ var NETWORK_FS_TYPES = new Set([
8930
8980
  1196443219,
8931
8981
  198183888
8932
8982
  ]);
8983
+ var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
8984
+ var onNetworkFs = (dir) => {
8985
+ if (process.platform !== "linux")
8986
+ return false;
8987
+ try {
8988
+ return isNetworkFsType(fs3.statfsSync(dir).type);
8989
+ } catch {
8990
+ return false;
8991
+ }
8992
+ };
8993
+ var LOCK_WAIT_MS = 5000;
8994
+ var LOCK_STALE_MS = 30000;
8995
+ var LOCK_OWNER = "owner";
8996
+ var lockToken = (lock) => {
8997
+ try {
8998
+ return fs3.readFileSync(path3.join(lock, LOCK_OWNER), "utf8");
8999
+ } catch {
9000
+ return null;
9001
+ }
9002
+ };
9003
+ var lockAgeMs = (lock) => {
9004
+ for (const target of [path3.join(lock, LOCK_OWNER), lock]) {
9005
+ try {
9006
+ return Date.now() - fs3.statSync(target).mtimeMs;
9007
+ } catch {}
9008
+ }
9009
+ return null;
9010
+ };
9011
+ var ledgerLock = {
9012
+ acquire(lock) {
9013
+ try {
9014
+ fs3.mkdirSync(lock);
9015
+ } catch (error) {
9016
+ if (error.code === "EEXIST")
9017
+ return null;
9018
+ throw error;
9019
+ }
9020
+ const token = `${process.pid}-${randomBytes(8).toString("hex")}`;
9021
+ fs3.writeFileSync(path3.join(lock, LOCK_OWNER), token);
9022
+ return token;
9023
+ },
9024
+ release(lock, token) {
9025
+ if (lockToken(lock) !== token)
9026
+ return false;
9027
+ const tomb = `${lock}.release-${randomBytes(6).toString("hex")}`;
9028
+ try {
9029
+ fs3.renameSync(lock, tomb);
9030
+ } catch {
9031
+ return false;
9032
+ }
9033
+ if (lockToken(tomb) !== token) {
9034
+ try {
9035
+ fs3.renameSync(tomb, lock);
9036
+ } catch {}
9037
+ return false;
9038
+ }
9039
+ fs3.rmSync(tomb, { recursive: true, force: true });
9040
+ return true;
9041
+ },
9042
+ token: lockToken,
9043
+ reap(lock, observed) {
9044
+ const tomb = `${lock}.reap-${randomBytes(6).toString("hex")}`;
9045
+ try {
9046
+ fs3.renameSync(lock, tomb);
9047
+ } catch {
9048
+ return false;
9049
+ }
9050
+ if (lockToken(tomb) === observed) {
9051
+ fs3.rmSync(tomb, { recursive: true, force: true });
9052
+ return true;
9053
+ }
9054
+ try {
9055
+ fs3.renameSync(tomb, lock);
9056
+ } catch {
9057
+ fs3.rmSync(tomb, { recursive: true, force: true });
9058
+ }
9059
+ return false;
9060
+ }
9061
+ };
9062
+ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
9063
+ const lock = `${file}.lock`;
9064
+ const deadline = Date.now() + waitMs;
9065
+ let token = null;
9066
+ for (;; ) {
9067
+ token = ledgerLock.acquire(lock);
9068
+ if (token)
9069
+ break;
9070
+ const observed = lockToken(lock);
9071
+ const age = lockAgeMs(lock);
9072
+ if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
9073
+ continue;
9074
+ if (Date.now() >= deadline)
9075
+ return null;
9076
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
9077
+ }
9078
+ try {
9079
+ return fn();
9080
+ } finally {
9081
+ ledgerLock.release(lock, token);
9082
+ }
9083
+ }
9084
+ function appendLine(file, line, options) {
9085
+ const write = () => {
9086
+ const fd = fs3.openSync(file, "a+");
9087
+ try {
9088
+ const size = fs3.fstatSync(fd).size;
9089
+ let fence = "";
9090
+ if (size > 0) {
9091
+ const last = Buffer.alloc(1);
9092
+ fs3.readSync(fd, last, 0, 1, size - 1);
9093
+ if (last[0] !== 10)
9094
+ fence = `
9095
+ `;
9096
+ }
9097
+ const bytes = Buffer.from(fence + line);
9098
+ const written = fs3.writeSync(fd, bytes);
9099
+ if (written !== bytes.length)
9100
+ return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
9101
+ return { ok: true, value: undefined };
9102
+ } finally {
9103
+ fs3.closeSync(fd);
9104
+ }
9105
+ };
9106
+ try {
9107
+ fs3.mkdirSync(path3.dirname(file), { recursive: true });
9108
+ if (!options.forceLock && !onNetworkFs(path3.dirname(file)))
9109
+ return write();
9110
+ const locked = withLedgerLock(file, write);
9111
+ return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
9112
+ } catch (error) {
9113
+ return err("unavailable", `cannot append to ${file}: ${error.message}`);
9114
+ }
9115
+ }
9116
+ var serialize = (row, now) => {
9117
+ const full = { v: LEDGER_VERSION, id: newId2(), at: now.toISOString(), ...row };
9118
+ return { full, line: `${JSON.stringify(full)}
9119
+ ` };
9120
+ };
9121
+ function appendRaw(cwd, row, options) {
9122
+ const { full, line } = serialize(row, options.now ?? new Date);
9123
+ const bytes = Buffer.byteLength(line);
9124
+ if (bytes > MAX_LINE_BYTES)
9125
+ 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>`);
9126
+ const file = ledgerPath(cwd);
9127
+ if (!file.ok)
9128
+ return file;
9129
+ const written = appendLine(file.value, line, options);
9130
+ return written.ok ? { ok: true, value: full } : written;
9131
+ }
9132
+ function appendHookObserved(cwd, row) {
9133
+ return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
9134
+ }
8933
9135
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8934
9136
  var str = (value) => typeof value === "string" ? value : null;
8935
9137
  var normalizeActor = (value) => {
@@ -10099,6 +10301,7 @@ var resolveCommitPolicy = (config, workspace) => {
10099
10301
  };
10100
10302
 
10101
10303
  // packages/workit-core/src/core/workspaces.ts
10304
+ import { spawnSync as spawnSync5 } from "node:child_process";
10102
10305
  import { createHash as createHash5 } from "node:crypto";
10103
10306
  import { readFileSync as readFileSync2, realpathSync } from "node:fs";
10104
10307
  import path6 from "node:path";
@@ -10410,6 +10613,53 @@ var validateWorkspaceGlob = (glob) => {
10410
10613
  }
10411
10614
  return { ok: true };
10412
10615
  };
10616
+
10617
+ class WorkspaceConfigError extends Error {
10618
+ name = "WorkspaceConfigError";
10619
+ }
10620
+ var mainCheckouts = new Map;
10621
+ var mainCheckoutOf = (cwd) => {
10622
+ const cached = mainCheckouts.get(cwd);
10623
+ if (cached !== undefined)
10624
+ return cached;
10625
+ const env = { ...process.env };
10626
+ for (const name of ["GIT_DIR", "GIT_WORK_TREE", "GIT_COMMON_DIR", "GIT_INDEX_FILE"])
10627
+ delete env[name];
10628
+ const git = (args) => {
10629
+ const run = spawnSync5("git", args, {
10630
+ cwd,
10631
+ env,
10632
+ encoding: "utf8",
10633
+ timeout: 5000,
10634
+ windowsHide: true
10635
+ });
10636
+ return run.status === 0 ? run.stdout : null;
10637
+ };
10638
+ let main = null;
10639
+ const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
10640
+ const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
10641
+ if (gitDir && common && path6.resolve(gitDir) !== path6.resolve(common)) {
10642
+ const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
10643
+ main = listed?.trim() || (path6.basename(common) === ".git" ? path6.dirname(common) : common);
10644
+ }
10645
+ mainCheckouts.set(cwd, main);
10646
+ return main;
10647
+ };
10648
+ var matchingEntries = (cwd, entries) => {
10649
+ const match = (where) => {
10650
+ const targets = [where, realpathOf(where)].map((value) => value.replaceAll("\\", "/"));
10651
+ return entries.filter((entry) => {
10652
+ const glob = entry.glob.replaceAll("\\", "/");
10653
+ const canonical = canonicalGlob(glob);
10654
+ return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
10655
+ });
10656
+ };
10657
+ const direct = match(cwd);
10658
+ if (direct.length)
10659
+ return direct;
10660
+ const main = mainCheckoutOf(cwd);
10661
+ return main ? match(main) : direct;
10662
+ };
10413
10663
  var realpathOf = (p) => {
10414
10664
  const real = realpathSync.native ?? realpathSync;
10415
10665
  try {
@@ -10434,43 +10684,29 @@ var selectWorkspaceMatch = (matches, cwd, workspaceName) => {
10434
10684
  if (named.length === 1)
10435
10685
  return named[0];
10436
10686
  if (named.length > 1)
10437
- throw new Error(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
10687
+ throw new WorkspaceConfigError(`ambiguous workspace ${JSON.stringify(workspaceName)} for ${cwd}`);
10438
10688
  if (matches.length > 0) {
10439
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
10689
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}; matching choices: ${matches.map((entry) => entry.name).join(", ")}`);
10440
10690
  }
10441
- throw new Error(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
10691
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(workspaceName)} does not match ${cwd}`);
10442
10692
  }
10443
10693
  if (matches.length < 2)
10444
10694
  return matches[0];
10445
10695
  const highestSpecificity = Math.max(...matches.map((entry) => globSpecificity(entry.glob)));
10446
10696
  const mostSpecific = matches.filter((entry) => globSpecificity(entry.glob) === highestSpecificity);
10447
10697
  if (mostSpecific.length > 1) {
10448
- throw new Error(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
10698
+ throw new WorkspaceConfigError(`ambiguous workspace for ${cwd}; choose one of: ${mostSpecific.map((entry) => entry.name).join(", ")}`);
10449
10699
  }
10450
10700
  return mostSpecific[0];
10451
10701
  };
10452
10702
  var resolveWorkspaceFromEntries = (cwd, entries, workspaceName) => {
10453
- const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
10454
- const matches = [];
10455
- for (const ws of entries) {
10456
- const glob = ws.glob.replaceAll("\\", "/");
10457
- const canonical = canonicalGlob(glob);
10458
- let matched = false;
10459
- for (const target of targets) {
10460
- if (matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target)) {
10461
- matched = true;
10462
- break;
10463
- }
10464
- }
10465
- if (matched)
10466
- matches.push(ws);
10467
- }
10703
+ const matches = matchingEntries(cwd, entries);
10468
10704
  return selectWorkspaceMatch(matches, cwd, workspaceName) ?? null;
10469
10705
  };
10470
10706
  var resolveWorkspaceFrom = (cwd, dir, workspaceName) => {
10471
10707
  const result = readWorkspacesResult(dir);
10472
10708
  if (result.status === "malformed" || result.status === "invalid")
10473
- throw new Error(result.error);
10709
+ throw new WorkspaceConfigError(result.error);
10474
10710
  return resolveWorkspaceFromEntries(cwd, result.entries, workspaceName);
10475
10711
  };
10476
10712
  var runtimeWorkspacePolicySchema = (kind) => {
@@ -10490,6 +10726,7 @@ var runtimeWorkspacePolicySchema = (kind) => {
10490
10726
  }).passthrough();
10491
10727
  return entrySchema;
10492
10728
  };
10729
+ var resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs");
10493
10730
  var runtimeWorkspaceIndexSchema = object({
10494
10731
  workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
10495
10732
  }).passthrough();
@@ -10501,31 +10738,26 @@ var matchRuntimeWorkspace = (cwd) => {
10501
10738
  } catch (error) {
10502
10739
  if (error.code === "ENOENT")
10503
10740
  return null;
10504
- throw new Error(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
10741
+ throw new WorkspaceConfigError(`${file} could not be read: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
10505
10742
  }
10506
10743
  let parsed;
10507
10744
  try {
10508
10745
  parsed = JSON.parse(raw);
10509
10746
  } catch {
10510
- throw new Error(`${file} is not valid JSON`);
10747
+ throw new WorkspaceConfigError(`${file} is not valid JSON`);
10511
10748
  }
10512
10749
  const validated = runtimeWorkspaceIndexSchema.safeParse(parsed);
10513
10750
  if (!validated.success) {
10514
10751
  const detail = validated.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspaces"}: ${issue.message}`).join("; ");
10515
- throw new Error(`${file} has invalid workspace matching data: ${detail}`);
10752
+ throw new WorkspaceConfigError(`${file} has invalid workspace matching data: ${detail}`);
10516
10753
  }
10517
10754
  const entries = validated.data.workspaces;
10518
10755
  for (const entry of entries ?? []) {
10519
10756
  const glob = validateWorkspaceGlob(entry.glob);
10520
10757
  if (!glob.ok)
10521
- throw new Error(`${file} workspace ${entry.name}: ${glob.error}`);
10758
+ throw new WorkspaceConfigError(`${file} workspace ${entry.name}: ${glob.error}`);
10522
10759
  }
10523
- const targets = [cwd, realpathOf(cwd)].map((value) => value.replaceAll("\\", "/"));
10524
- const matches = (entries ?? []).filter((entry) => {
10525
- const glob = entry.glob.replaceAll("\\", "/");
10526
- const canonical = canonicalGlob(glob);
10527
- return targets.some((target) => matchWorkspace(glob, target) || canonical !== glob && matchWorkspace(canonical, target));
10528
- });
10760
+ const matches = matchingEntries(cwd, entries ?? []);
10529
10761
  const workspaceName = process.env.WORKFLOW_WORKSPACE_NAME?.trim() || undefined;
10530
10762
  const selected = selectWorkspaceMatch(matches, cwd, workspaceName);
10531
10763
  return selected ? { file, selected } : null;
@@ -10542,7 +10774,7 @@ var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
10542
10774
  const policy = runtimeWorkspacePolicySchema(kind).safeParse(selected);
10543
10775
  if (!policy.success) {
10544
10776
  const detail = policy.error.issues.map((issue) => `${issue.path.map(String).join(".") || "workspace"}: ${issue.message}`).join("; ");
10545
- throw new Error(`${file} has invalid ${kind} policy configuration: ${detail}`);
10777
+ throw new WorkspaceConfigError(`${file} has invalid ${kind} policy configuration: ${detail}`);
10546
10778
  }
10547
10779
  return policy.data;
10548
10780
  };
@@ -10553,10 +10785,10 @@ var resolveRuntimeWorkspacePolicy = (cwd, kind, requestedProfile) => {
10553
10785
  const key = kind === "branch" ? "branchPolicy" : "commitPolicy";
10554
10786
  const profileName = requestedProfile?.trim() || candidate.defaultProfile;
10555
10787
  if (candidate.defaultProfile && !Object.hasOwn(candidate.profiles ?? {}, candidate.defaultProfile))
10556
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
10788
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(candidate.defaultProfile)}`);
10557
10789
  const profile = profileName ? candidate.profiles?.[profileName] : undefined;
10558
10790
  if (profileName && !profile)
10559
- throw new Error(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
10791
+ throw new WorkspaceConfigError(`workspace ${JSON.stringify(candidate.name)} has no profile ${JSON.stringify(profileName)}`);
10560
10792
  const profilePolicy = profile?.[key];
10561
10793
  const workspacePolicy = candidate[key];
10562
10794
  return {
@@ -11321,7 +11553,7 @@ var packageRoot = () => {
11321
11553
  };
11322
11554
 
11323
11555
  // packages/workit-core/src/core/store-lock.ts
11324
- import { spawnSync as spawnSync5 } from "node:child_process";
11556
+ import { spawnSync as spawnSync6 } from "node:child_process";
11325
11557
  import fs7 from "node:fs";
11326
11558
  import { hostname } from "node:os";
11327
11559
  var metadataLockSchema = object({
@@ -11371,7 +11603,7 @@ var processStartOf = (pid) => {
11371
11603
  }
11372
11604
  if (process.platform === "darwin" || process.platform === "freebsd") {
11373
11605
  try {
11374
- const run = spawnSync5("ps", ["-o", "lstart=", "-p", String(pid)], {
11606
+ const run = spawnSync6("ps", ["-o", "lstart=", "-p", String(pid)], {
11375
11607
  encoding: "utf8",
11376
11608
  timeout: 1000
11377
11609
  });
@@ -11983,9 +12215,10 @@ var drop = (lock) => {
11983
12215
  };
11984
12216
  var envActor = () => {
11985
12217
  const value = (key) => process.env[key]?.trim() || null;
12218
+ const acting = hostSessionFromEnv(process.env);
11986
12219
  return {
11987
- host: value("WORKIT_HOST") ?? "runtime",
11988
- session: value("WORKIT_SESSION_ID"),
12220
+ host: acting.host ?? "runtime",
12221
+ session: acting.session,
11989
12222
  agentId: value("WORKIT_AGENT_ID")
11990
12223
  };
11991
12224
  };
@@ -13511,11 +13744,11 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
13511
13744
  // packages/workit-core/src/core/task-evaluation.ts
13512
13745
  import { createHash as createHash8 } from "node:crypto";
13513
13746
  import * as fs11 from "node:fs";
13514
- import { spawnSync as spawnSync7 } from "node:child_process";
13747
+ import { spawnSync as spawnSync8 } from "node:child_process";
13515
13748
  import path14 from "node:path";
13516
13749
 
13517
13750
  // packages/workit-core/src/check-config.ts
13518
- import { spawnSync as spawnSync6 } from "node:child_process";
13751
+ import { spawnSync as spawnSync7 } from "node:child_process";
13519
13752
  import fs10 from "node:fs";
13520
13753
  import path13 from "node:path";
13521
13754
  var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
@@ -13559,7 +13792,7 @@ function splitCommand(command) {
13559
13792
  return argv.length ? argv : new Error("empty command");
13560
13793
  }
13561
13794
  var git3 = (cwd, args) => {
13562
- const run = spawnSync6("git", args, {
13795
+ const run = spawnSync7("git", args, {
13563
13796
  cwd,
13564
13797
  encoding: "utf8",
13565
13798
  timeout: 20000,
@@ -13764,7 +13997,7 @@ var compareCodeUnits3 = (left, right) => {
13764
13997
  return left.length - right.length;
13765
13998
  };
13766
13999
  var gitPaths = (root) => {
13767
- const result = spawnSync7("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
14000
+ const result = spawnSync8("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13768
14001
  cwd: root,
13769
14002
  encoding: "buffer"
13770
14003
  });
@@ -13777,7 +14010,7 @@ var gitPaths = (root) => {
13777
14010
  };
13778
14011
  }
13779
14012
  const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path14.sep).join("/")) : [];
13780
- const stagedDeleted = spawnSync7("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
14013
+ const stagedDeleted = spawnSync8("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13781
14014
  if (stagedDeleted.status !== 0) {
13782
14015
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
13783
14016
  if (!/not a git repository/i.test(stderr))
@@ -13908,7 +14141,7 @@ function captureCandidate(root, scope, environment = []) {
13908
14141
  const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
13909
14142
  if (values.some(({ value }) => value === null))
13910
14143
  completeness = "uncertain";
13911
- const headResult = spawnSync7("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
14144
+ const headResult = spawnSync8("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13912
14145
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
13913
14146
  completeness = "uncertain";
13914
14147
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -16599,6 +16832,7 @@ var validateBranchNameFor = (workspaceRoot, name) => {
16599
16832
  };
16600
16833
  }
16601
16834
  };
16835
+ var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
16602
16836
 
16603
16837
  // packages/workit-core/src/core/route-intent.ts
16604
16838
  var shellBranchTarget = (command) => {
@@ -16624,36 +16858,43 @@ var shellPolicy = (cwd, command) => {
16624
16858
  // packages/workit-core/src/hooks/write-gate.ts
16625
16859
  import { realpathSync as realpathSync5 } from "node:fs";
16626
16860
  import path16 from "node:path";
16627
- var NONE2 = { kind: "none" };
16628
- var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
16629
- var canonical2 = (file) => {
16630
- let rest = "";
16631
- for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
16632
- try {
16633
- return path16.join(realpathSync5(dir), rest);
16634
- } catch {
16635
- if (path16.dirname(dir) === dir)
16636
- return path16.resolve(file);
16637
- rest = rest ? path16.join(path16.basename(dir), rest) : path16.basename(dir);
16861
+
16862
+ // packages/workit-core/src/hooks/shell-words.ts
16863
+ var closingQuote = (command, start, dialect) => {
16864
+ const quote = command[start];
16865
+ for (let index = start + 1;index < command.length; index++) {
16866
+ const char = command[index];
16867
+ if (quote === "'") {
16868
+ if (char !== "'")
16869
+ continue;
16870
+ if (dialect === "powershell" && command[index + 1] === "'") {
16871
+ index++;
16872
+ continue;
16873
+ }
16874
+ return index;
16875
+ }
16876
+ if (dialect === "posix" && char === "\\" || dialect === "powershell" && char === "`") {
16877
+ index++;
16878
+ continue;
16638
16879
  }
16880
+ if (char === '"')
16881
+ return index;
16639
16882
  }
16883
+ return -1;
16640
16884
  };
16641
- var exempt = (cwd, root, file, plans) => {
16642
- const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
16643
- const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
16644
- if (relative.startsWith("..") || path16.isAbsolute(relative))
16645
- return true;
16646
- const posix = relative.split(path16.sep).join("/");
16647
- const segments = posix.split("/");
16648
- return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
16885
+ var unquote = (body, quote, dialect) => {
16886
+ if (quote === "'")
16887
+ return dialect === "powershell" ? body.replaceAll("''", "'") : body;
16888
+ return dialect === "posix" ? body.replace(/\\(["\\$`])/g, "$1") : body.replace(/`(.)/g, "$1");
16649
16889
  };
16650
- var segmentsOf = (command) => {
16890
+ var segmentsOf = (command, dialect = "posix") => {
16651
16891
  const segments = [];
16652
16892
  let current = { words: [], redirects: [] };
16653
16893
  let word = "";
16654
16894
  let inWord = false;
16655
16895
  let pending = null;
16656
16896
  const heredocs = [];
16897
+ const escape = dialect === "posix" ? "\\" : "`";
16657
16898
  const flushWord = () => {
16658
16899
  if (!inWord)
16659
16900
  return;
@@ -16677,11 +16918,11 @@ var segmentsOf = (command) => {
16677
16918
  for (let index = 0;index < command.length; index++) {
16678
16919
  const char = command[index];
16679
16920
  if (char === "'" || char === '"') {
16680
- const end = command.indexOf(char, index + 1);
16681
- word += command.slice(index + 1, end < 0 ? undefined : end);
16921
+ const end = closingQuote(command, index, dialect);
16922
+ word += unquote(command.slice(index + 1, end < 0 ? undefined : end), char, dialect);
16682
16923
  inWord = true;
16683
16924
  index = end < 0 ? command.length : end;
16684
- } else if (char === "\\" && index + 1 < command.length) {
16925
+ } else if (char === escape && index + 1 < command.length) {
16685
16926
  word += command[++index];
16686
16927
  inWord = true;
16687
16928
  } else if (char === `
@@ -16698,7 +16939,14 @@ var segmentsOf = (command) => {
16698
16939
  }
16699
16940
  } else if (/\s/.test(char))
16700
16941
  flushWord();
16701
- else if (";&|()".includes(char)) {
16942
+ else if (char === "(" || char === ")") {
16943
+ if (char === "(" && inWord && word.endsWith("$")) {
16944
+ word = word.slice(0, -1);
16945
+ inWord = word !== "";
16946
+ }
16947
+ flushSegment();
16948
+ segments.push({ words: [], redirects: [], group: char === "(" ? "open" : "close" });
16949
+ } else if (";&|".includes(char)) {
16702
16950
  if (char === "&" && command[index + 1] === ">")
16703
16951
  continue;
16704
16952
  flushSegment();
@@ -16716,7 +16964,7 @@ var segmentsOf = (command) => {
16716
16964
  inWord = true;
16717
16965
  }
16718
16966
  pending = "redirect";
16719
- } else if (char === "<") {
16967
+ } else if (char === "<" && dialect === "posix") {
16720
16968
  flushWord();
16721
16969
  if (command[index + 1] === "<") {
16722
16970
  index++;
@@ -16733,6 +16981,31 @@ var segmentsOf = (command) => {
16733
16981
  flushSegment();
16734
16982
  return segments;
16735
16983
  };
16984
+
16985
+ // packages/workit-core/src/hooks/write-gate.ts
16986
+ var NONE2 = { kind: "none" };
16987
+ var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
16988
+ var canonical2 = (file) => {
16989
+ let rest = "";
16990
+ for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
16991
+ try {
16992
+ return path16.join(realpathSync5(dir), rest);
16993
+ } catch {
16994
+ if (path16.dirname(dir) === dir)
16995
+ return path16.resolve(file);
16996
+ rest = rest ? path16.join(path16.basename(dir), rest) : path16.basename(dir);
16997
+ }
16998
+ }
16999
+ };
17000
+ var exempt = (cwd, root, file, plans) => {
17001
+ const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
17002
+ const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
17003
+ if (relative.startsWith("..") || path16.isAbsolute(relative))
17004
+ return true;
17005
+ const posix = relative.split(path16.sep).join("/");
17006
+ const segments = posix.split("/");
17007
+ return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
17008
+ };
16736
17009
  var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
16737
17010
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
16738
17011
  var editsOf = (input) => {
@@ -16882,14 +17155,473 @@ function writeGate(cwd, paths) {
16882
17155
  return NONE2;
16883
17156
  }
16884
17157
  }
17158
+ // packages/workit-core/src/hooks/raw-git.ts
17159
+ import { spawnSync as spawnSync9 } from "node:child_process";
17160
+ import { createHash as createHash10 } from "node:crypto";
17161
+ import fs12 from "node:fs";
17162
+ import os4 from "node:os";
17163
+ import path17 from "node:path";
17164
+ var NONE3 = { kind: "none" };
17165
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
17166
+ var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
17167
+ var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
17168
+ var CD = new Set(["cd", "chdir", "set-location", "sl"]);
17169
+ var PUSHD = new Set(["pushd", "push-location"]);
17170
+ var POPD = new Set(["popd", "pop-location"]);
17171
+ var GIT_VALUE_OPTIONS = new Set([
17172
+ "-c",
17173
+ "--git-dir",
17174
+ "--work-tree",
17175
+ "--namespace",
17176
+ "--exec-path",
17177
+ "--config-env",
17178
+ "--super-prefix"
17179
+ ]);
17180
+ var isAbsolute = (value) => path17.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
17181
+ var joinDir = (base, next) => {
17182
+ if (base === undefined || /[$`*?]/.test(next) || next === "-")
17183
+ return;
17184
+ const expanded = next === "~" || next.startsWith("~/") ? path17.join(os4.homedir(), next.slice(1)) : next;
17185
+ return base === null || isAbsolute(expanded) ? expanded : path17.join(base, expanded);
17186
+ };
17187
+ var cdTarget = (words) => {
17188
+ const args = words.slice(1);
17189
+ const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
17190
+ if (flag >= 0)
17191
+ return args[flag + 1] ?? null;
17192
+ return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
17193
+ };
17194
+ var stripPrefixes = (input) => {
17195
+ let words = input;
17196
+ for (;; ) {
17197
+ const first = words[0];
17198
+ if (first === undefined)
17199
+ return words;
17200
+ if (/^\w+=/.test(first))
17201
+ words = words.slice(1);
17202
+ else if (PREFIXES3.has(first)) {
17203
+ words = words.slice(1);
17204
+ while (words[0]?.startsWith("-"))
17205
+ words = words.slice(words[0] === "-u" ? 2 : 1);
17206
+ } else if (first === "timeout") {
17207
+ words = words.slice(1);
17208
+ while (words[0]?.startsWith("-"))
17209
+ words = words.slice(1);
17210
+ words = words.slice(1);
17211
+ } else
17212
+ return words;
17213
+ }
17214
+ };
17215
+ var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
17216
+ function rawInvocations(command, dialect = "posix", depth = 0) {
17217
+ const found = [];
17218
+ let dir = null;
17219
+ const groups = [];
17220
+ let stack = [];
17221
+ for (const segment of segmentsOf(command, dialect)) {
17222
+ if (segment.group === "open") {
17223
+ groups.push({ dir, stack: [...stack] });
17224
+ continue;
17225
+ }
17226
+ if (segment.group === "close") {
17227
+ const saved = groups.pop();
17228
+ if (saved)
17229
+ ({ dir, stack } = saved);
17230
+ continue;
17231
+ }
17232
+ const words = stripPrefixes(segment.words);
17233
+ const name = commandName(words[0]);
17234
+ if (CD.has(name)) {
17235
+ const target = cdTarget(words);
17236
+ dir = target === null ? os4.homedir() : joinDir(dir, target);
17237
+ continue;
17238
+ }
17239
+ if (PUSHD.has(name)) {
17240
+ const target = cdTarget(words);
17241
+ stack.push(dir);
17242
+ dir = target === null || target === "-" ? undefined : joinDir(dir, target);
17243
+ continue;
17244
+ }
17245
+ if (POPD.has(name)) {
17246
+ dir = stack.length ? stack.pop() : undefined;
17247
+ continue;
17248
+ }
17249
+ if (SHELLS.has(name) && depth < 3) {
17250
+ const flag = words.findIndex((word, index) => index > 0 && SCRIPT_FLAG.test(word));
17251
+ const script = flag > 0 ? words[flag + 1] : undefined;
17252
+ if (script)
17253
+ for (const inner of rawInvocations(script, "posix", depth + 1))
17254
+ found.push({
17255
+ ...inner,
17256
+ dir: inner.dir === null ? dir : inner.dir === undefined ? undefined : joinDir(dir, inner.dir)
17257
+ });
17258
+ continue;
17259
+ }
17260
+ if (name === "gh" || name === "glab") {
17261
+ found.push({ tool: name, dir, args: words.slice(1) });
17262
+ continue;
17263
+ }
17264
+ if (name !== "git")
17265
+ continue;
17266
+ let here = dir;
17267
+ let index = 1;
17268
+ for (;index < words.length; index++) {
17269
+ const word = words[index];
17270
+ if (word === "-C")
17271
+ here = words[index + 1] ? joinDir(here, words[++index]) : here;
17272
+ else if (GIT_VALUE_OPTIONS.has(word))
17273
+ index++;
17274
+ else if (!word.startsWith("-"))
17275
+ break;
17276
+ }
17277
+ found.push({ tool: "git", dir: here, args: words.slice(index) });
17278
+ }
17279
+ return found;
17280
+ }
17281
+ var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17282
+ var pushAction = (args) => {
17283
+ let force = false;
17284
+ let lease = false;
17285
+ let wholesale = false;
17286
+ let remote = null;
17287
+ const positionals = [];
17288
+ for (let index = 1;index < args.length; index++) {
17289
+ const arg = args[index];
17290
+ if (arg === "--") {
17291
+ positionals.push(...args.slice(index + 1));
17292
+ break;
17293
+ }
17294
+ if (arg === "--dry-run" || /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg))
17295
+ return null;
17296
+ if (arg === "--repo")
17297
+ remote = args[++index] ?? null;
17298
+ else if (arg.startsWith("--repo="))
17299
+ remote = arg.slice("--repo=".length);
17300
+ else if (PUSH_VALUE_OPTIONS.has(arg))
17301
+ index++;
17302
+ else if (arg === "--force" || /^-[a-zA-Z]*f[a-zA-Z]*$/.test(arg))
17303
+ force = true;
17304
+ else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
17305
+ lease = true;
17306
+ else if (["--all", "--mirror", "--branches", "--tags"].includes(arg))
17307
+ wholesale = true;
17308
+ else if (!arg.startsWith("-"))
17309
+ positionals.push(arg);
17310
+ }
17311
+ const specs = remote === null ? positionals.slice(1) : positionals;
17312
+ remote ??= positionals[0] ?? null;
17313
+ const targets = [];
17314
+ for (const raw of specs) {
17315
+ if (raw.startsWith("+"))
17316
+ force = true;
17317
+ const spec = raw.replace(/^\+/, "");
17318
+ const colon = spec.indexOf(":");
17319
+ const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
17320
+ if (!destination)
17321
+ continue;
17322
+ if (destination === "HEAD")
17323
+ targets.push(null);
17324
+ else if (destination.startsWith("refs/heads/"))
17325
+ targets.push(destination.slice(11));
17326
+ else if (!destination.startsWith("refs/"))
17327
+ targets.push(destination);
17328
+ }
17329
+ if (specs.length === 0 && !wholesale)
17330
+ targets.push(null);
17331
+ return { kind: "push", targets, blindForce: force && !lease, remote };
17332
+ };
17333
+ var VALUE_OPTIONS = {
17334
+ git: new Set([
17335
+ "-m",
17336
+ "--message",
17337
+ "-F",
17338
+ "--file",
17339
+ "-C",
17340
+ "-c",
17341
+ "--reuse-message",
17342
+ "--reedit-message",
17343
+ "--author",
17344
+ "--date",
17345
+ "--trailer",
17346
+ "-t",
17347
+ "--template",
17348
+ "--cleanup",
17349
+ "--fixup",
17350
+ "--squash",
17351
+ "-o",
17352
+ "--push-option",
17353
+ "--repo",
17354
+ "--receive-pack",
17355
+ "--exec"
17356
+ ]),
17357
+ 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"])),
17358
+ 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"]))
17359
+ };
17360
+ var asksHelp = (invocation) => {
17361
+ const { tool, args } = invocation;
17362
+ if (args[0] === "help" || tool !== "git" && args[1] === "help")
17363
+ return true;
17364
+ const values = VALUE_OPTIONS[tool];
17365
+ for (let index = 0;index < args.length; index++) {
17366
+ const arg = args[index];
17367
+ if (arg === "--")
17368
+ return false;
17369
+ if (arg === "--help" || arg === "-h")
17370
+ return true;
17371
+ if (values.has(arg))
17372
+ index++;
17373
+ }
17374
+ return false;
17375
+ };
17376
+ function rawAction(invocation) {
17377
+ const args = invocation.args;
17378
+ if (asksHelp(invocation))
17379
+ return null;
17380
+ const [first, second] = args;
17381
+ if (invocation.tool === "git") {
17382
+ if (first === "commit")
17383
+ return args.includes("--dry-run") ? null : { kind: "commit", amend: args.includes("--amend") };
17384
+ if (first === "push")
17385
+ return pushAction(args);
17386
+ return null;
17387
+ }
17388
+ const forge = invocation.tool;
17389
+ const noun = forge === "gh" ? "pr" : "mr";
17390
+ if (first !== noun)
17391
+ return null;
17392
+ if (second === "merge")
17393
+ return { kind: "merge", forge };
17394
+ if (second === "create")
17395
+ return { kind: "pr-create", forge };
17396
+ if (["view", "checks", "status", "ci", "list"].includes(second ?? ""))
17397
+ return { kind: "pr-read", forge };
17398
+ return null;
17399
+ }
17400
+ var repoAt = (dir) => {
17401
+ const location = resolveStore(dir);
17402
+ if (location instanceof Error || !location.shared)
17403
+ return null;
17404
+ return { location, dir, branch: resolveTaskKey(dir, location).branch };
17405
+ };
17406
+ var isWorkitWorkspace = (repo) => {
17407
+ if (fs12.existsSync(repo.location.dir))
17408
+ return true;
17409
+ try {
17410
+ return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
17411
+ } catch {
17412
+ return false;
17413
+ }
17414
+ };
17415
+ var guardedBranch = (dir, branch) => {
17416
+ try {
17417
+ const policy = resolveBranchPolicyFor(dir);
17418
+ return policy.protected.has(branch.toLowerCase()) || policy.defaultTargetBranch.toLowerCase() === branch.toLowerCase();
17419
+ } catch {
17420
+ return false;
17421
+ }
17422
+ };
17423
+ var toPushRemote = (repo, remote) => {
17424
+ if (remote === null)
17425
+ return true;
17426
+ try {
17427
+ const pushRemote = pushRemoteName(repo.dir, repo.branch);
17428
+ return pushRemote === null || pushRemote === remote;
17429
+ } catch {
17430
+ return false;
17431
+ }
17432
+ };
17433
+ var headSha2 = (repo) => {
17434
+ const git = repo.location.git;
17435
+ if (!git || git.reftable)
17436
+ return null;
17437
+ try {
17438
+ const head = fs12.readFileSync(path17.join(git.gitDir, "HEAD"), "utf8").trim();
17439
+ if (/^[0-9a-f]{40,64}$/.test(head))
17440
+ return head;
17441
+ const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
17442
+ if (!ref)
17443
+ return null;
17444
+ const common = path17.dirname(repo.location.dir);
17445
+ try {
17446
+ return fs12.readFileSync(path17.join(common, ref), "utf8").trim() || null;
17447
+ } catch {}
17448
+ const packed = fs12.readFileSync(path17.join(common, "packed-refs"), "utf8");
17449
+ return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
17450
+ } catch {
17451
+ return null;
17452
+ }
17453
+ };
17454
+ var quote = (text) => `\`${text}\``;
17455
+ var refusal = (action, branch) => {
17456
+ if (action.kind === "merge")
17457
+ 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")}).`;
17458
+ 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")}.`;
17459
+ };
17460
+ var nudgeFor = (action, host, session) => {
17461
+ switch (action.kind) {
17462
+ case "commit": {
17463
+ const prefix = host === "cursor" && session ? `WORKIT_SESSION_ID=${session} ` : "";
17464
+ 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.`;
17465
+ }
17466
+ case "push":
17467
+ 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.`;
17468
+ case "pr-create":
17469
+ return `workit: prefer ${quote("workit pr create --fill")} (or --title/--body): it records the PR the merge gate checks.`;
17470
+ case "pr-read":
17471
+ return `workit: ${quote("workit pr status")} shows the PR, its checks and the verdict state; ${quote("workit ci wait")} waits for checks.`;
17472
+ case "merge":
17473
+ return null;
17474
+ }
17475
+ };
17476
+ var actionsIn = (cwd, command, dialect) => {
17477
+ const out = [];
17478
+ const repos = new Map;
17479
+ for (const invocation of rawInvocations(command, dialect)) {
17480
+ if (invocation.dir === undefined)
17481
+ continue;
17482
+ const action = rawAction(invocation);
17483
+ if (!action)
17484
+ continue;
17485
+ const dir = path17.resolve(cwd, invocation.dir ?? ".");
17486
+ if (!repos.has(dir)) {
17487
+ const repo = repoAt(dir);
17488
+ repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
17489
+ }
17490
+ const repo = repos.get(dir);
17491
+ if (repo)
17492
+ out.push({ action, repo });
17493
+ }
17494
+ return out;
17495
+ };
17496
+ var mentionsRawTool = (command) => /\b(?:git|gh|glab)\b/i.test(command);
17497
+ var dialectOf = (input) => (input.event.kind === "shell.pre" || input.event.kind === "shell.post" ? input.event.dialect : undefined) ?? "posix";
17498
+ function rawGitPre(input, command) {
17499
+ try {
17500
+ if (!mentionsRawTool(command))
17501
+ return NONE3;
17502
+ const nudges = [];
17503
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
17504
+ if (action.kind === "merge")
17505
+ return deny(refusal(action, null));
17506
+ if (action.kind === "push")
17507
+ for (const target of action.targets) {
17508
+ const branch = target ?? repo.branch;
17509
+ if (branch && guardedBranch(repo.dir, branch) && toPushRemote(repo, action.remote))
17510
+ return deny(refusal(action, branch));
17511
+ }
17512
+ const nudge = nudgeFor(action, input.host, input.session.id);
17513
+ if (nudge && !nudges.includes(nudge))
17514
+ nudges.push(nudge);
17515
+ }
17516
+ return nudges.length ? { kind: "context", text: nudges.join(`
17517
+ `) } : NONE3;
17518
+ } catch {
17519
+ return NONE3;
17520
+ }
17521
+ }
17522
+ var deny = (reason) => ({ kind: "deny", reason, unblock: null });
17523
+ var NEXT_COMMAND = "next";
17524
+ var MARKER_TTL_SECONDS = 3600;
17525
+ var markerFile = (repo, session, key) => path17.join(repo.location.dir, "hooks", `commit-${createHash10("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
17526
+ var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash10("sha256").update(command).digest("hex")}`;
17527
+ function noteRawCommit(input, command, key) {
17528
+ try {
17529
+ if (!input.session.id || !/\bcommit\b/.test(command))
17530
+ return;
17531
+ const at = Math.floor(Date.now() / 1000);
17532
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input))) {
17533
+ if (action.kind !== "commit")
17534
+ continue;
17535
+ const file = markerFile(repo, input.session.id, key);
17536
+ fs12.mkdirSync(path17.dirname(file), { recursive: true });
17537
+ fs12.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
17538
+ `);
17539
+ }
17540
+ } catch {}
17541
+ }
17542
+ var headCommit = (dir) => {
17543
+ const run = spawnSync9("git", [
17544
+ "log",
17545
+ "-1",
17546
+ "--format=%H%x1f%ct%x1f%s%x1f%(trailers:key=Workit-Session,valueonly,separator=%x2c)"
17547
+ ], {
17548
+ cwd: dir,
17549
+ encoding: "utf8",
17550
+ timeout: 5000,
17551
+ windowsHide: true,
17552
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_OPTIONAL_LOCKS: "0", LC_ALL: "C" }
17553
+ });
17554
+ const [sha, committed, subject = "", trailer = ""] = (run.status === 0 ? run.stdout.trim() : "").split("\x1F");
17555
+ return sha && /^[0-9a-f]{40,64}$/.test(sha) ? { sha, committed: Number(committed), subject, trailer: trailer.trim() || null } : null;
17556
+ };
17557
+ var settle2 = (input, repo, file) => {
17558
+ let marker;
17559
+ try {
17560
+ marker = JSON.parse(fs12.readFileSync(file, "utf8"));
17561
+ } catch {
17562
+ return;
17563
+ }
17564
+ fs12.rmSync(file, { force: true });
17565
+ const session = input.session.id;
17566
+ const now = Date.now() / 1000;
17567
+ if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
17568
+ return;
17569
+ if (!session || !repo.branch)
17570
+ return;
17571
+ const head = headCommit(repo.dir);
17572
+ if (!head || head.sha === marker.head || head.committed < marker.at)
17573
+ return;
17574
+ if (head.trailer && head.trailer !== session)
17575
+ return;
17576
+ const ledger = readLedger(repo.dir);
17577
+ if (!ledger.ok)
17578
+ return;
17579
+ const claimed = ledger.value.rows.some((row) => row.type === "commit.recorded" && (row.sha === head.sha || row.head === head.sha));
17580
+ if (claimed)
17581
+ return;
17582
+ appendHookObserved(repo.dir, {
17583
+ type: "commit.recorded",
17584
+ actor: { host: input.host, session, agentId: input.session.agentId },
17585
+ branch: repo.branch,
17586
+ head: head.sha,
17587
+ sha: head.sha,
17588
+ session,
17589
+ agentId: input.session.agentId,
17590
+ subject: head.subject.slice(0, 200),
17591
+ files: [],
17592
+ fileCount: null,
17593
+ via: "raw_shell"
17594
+ });
17595
+ };
17596
+ function rawGitPost(input, command, key) {
17597
+ try {
17598
+ if (!input.session.id || !/\bcommit\b/.test(command))
17599
+ return;
17600
+ for (const { action, repo } of actionsIn(input.cwd, command, dialectOf(input)))
17601
+ if (action.kind === "commit")
17602
+ settle2(input, repo, markerFile(repo, input.session.id, key));
17603
+ } catch {}
17604
+ }
17605
+ function settlePendingCommit(input) {
17606
+ try {
17607
+ if (!input.session.id)
17608
+ return;
17609
+ const repo = repoAt(input.cwd);
17610
+ if (!repo)
17611
+ return;
17612
+ const file = markerFile(repo, input.session.id, NEXT_COMMAND);
17613
+ if (fs12.existsSync(file))
17614
+ settle2(input, repo, file);
17615
+ } catch {}
17616
+ }
16885
17617
  // packages/workit-core/src/hooks/hosts/fields.ts
16886
17618
  import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
16887
- import path17 from "node:path";
17619
+ import path18 from "node:path";
16888
17620
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16889
17621
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
16890
17622
  var optionalText = (value) => typeof value === "string" ? value : null;
16891
17623
  var existingDirectory = (value) => {
16892
- if (!nonEmpty2(value) || !path17.isAbsolute(value) || !existsSync6(value))
17624
+ if (!nonEmpty2(value) || !path18.isAbsolute(value) || !existsSync6(value))
16893
17625
  return null;
16894
17626
  try {
16895
17627
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -16897,8 +17629,8 @@ var existingDirectory = (value) => {
16897
17629
  return null;
16898
17630
  }
16899
17631
  };
16900
- var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
16901
- var SCRIPT_FLAG = /^(?:-\w*c|-command|\/c)$/i;
17632
+ var SHELLS2 = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
17633
+ var SCRIPT_FLAG2 = /^(?:-\w*c|-command|\/c)$/i;
16902
17634
  var quoteArg = (arg) => /^[\w@%+=:,./-]+$/.test(arg) ? arg : JSON.stringify(arg);
16903
17635
  var commandText = (value) => {
16904
17636
  if (nonEmpty2(value))
@@ -16909,9 +17641,9 @@ var commandText = (value) => {
16909
17641
  if (!argv.every((arg) => typeof arg === "string"))
16910
17642
  return null;
16911
17643
  const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
16912
- if (SHELLS.has(shell))
17644
+ if (SHELLS2.has(shell))
16913
17645
  for (let index = 1;index < argv.length - 1; index++) {
16914
- if (SCRIPT_FLAG.test(argv[index]))
17646
+ if (SCRIPT_FLAG2.test(argv[index]))
16915
17647
  return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
16916
17648
  if (!/^[-/]/.test(argv[index]))
16917
17649
  break;
@@ -16952,7 +17684,7 @@ var writePaths = (input) => {
16952
17684
  };
16953
17685
  // packages/workit-core/src/hooks/handle.ts
16954
17686
  var usable2 = (value) => value === "native" || value === "partial";
16955
- var NONE3 = { kind: "none" };
17687
+ var NONE4 = { kind: "none" };
16956
17688
  var offered = new Set;
16957
17689
  var CLAUDE_WORKTREE_IMPLEMENTER = "workit:implementer";
16958
17690
  var CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
@@ -16971,7 +17703,7 @@ function handleHook(input, deps) {
16971
17703
  switch (event.kind) {
16972
17704
  case "session.start": {
16973
17705
  if (!usable2(descriptor.context.sessionStart))
16974
- return NONE3;
17706
+ return NONE4;
16975
17707
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
16976
17708
  const offer = event.source === "startup" && (key === null || !offered.has(key));
16977
17709
  if (offer && key !== null)
@@ -16986,19 +17718,34 @@ function handleHook(input, deps) {
16986
17718
  }
16987
17719
  case "context.turn": {
16988
17720
  if (!usable2(descriptor.context.perTurn))
16989
- return NONE3;
17721
+ return NONE4;
16990
17722
  const text = turnContextText(input, descriptor);
16991
- return text ? { kind: "context", text } : NONE3;
17723
+ return text ? { kind: "context", text } : NONE4;
16992
17724
  }
16993
17725
  case "shell.pre": {
16994
- const policy = usable2(descriptor.shellPolicy.deny) ? shellPolicy(input.cwd, event.command) : NONE3;
16995
- if (policy.kind !== "none" || !usable2(descriptor.events["write.pre"].support))
17726
+ const canDeny = usable2(descriptor.shellPolicy.deny);
17727
+ const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE4;
17728
+ if (policy.kind !== "none")
16996
17729
  return policy;
16997
- const writes = shellWrites(event.command);
16998
- return writes.writes ? writeGate(input.cwd, writes.targets) : NONE3;
17730
+ const postTool = usable2(descriptor.events["shell.post"].support);
17731
+ if (!postTool)
17732
+ settlePendingCommit(input);
17733
+ const raw = canDeny ? rawGitPre(input, event.command) : NONE4;
17734
+ if (raw.kind === "deny")
17735
+ return raw;
17736
+ const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
17737
+ const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE4;
17738
+ if (gate.kind === "deny")
17739
+ return gate;
17740
+ noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
17741
+ return raw;
16999
17742
  }
17743
+ case "shell.post":
17744
+ if (usable2(descriptor.events["shell.post"].support))
17745
+ rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
17746
+ return NONE4;
17000
17747
  case "write.pre":
17001
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE3;
17748
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE4;
17002
17749
  case "subagent.start":
17003
17750
  return {
17004
17751
  kind: "context",
@@ -17010,19 +17757,18 @@ function handleHook(input, deps) {
17010
17757
  userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
17011
17758
  };
17012
17759
  case "tool.pre":
17013
- case "shell.post":
17014
17760
  case "subagent.stop":
17015
17761
  case "prompt.submit":
17016
17762
  case "stop":
17017
- return NONE3;
17763
+ return NONE4;
17018
17764
  }
17019
17765
  }
17020
17766
  var failureDecision = (descriptor, event, error) => {
17021
17767
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
17022
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE3;
17768
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE4;
17023
17769
  if (event === "session.start" || event === "subagent.start")
17024
17770
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
17025
- return NONE3;
17771
+ return NONE4;
17026
17772
  };
17027
17773
  var dispatchHook = (adapter, raw, env = process.env) => {
17028
17774
  const parsed = adapter.parse(raw, env);
@@ -17145,6 +17891,7 @@ var EVENTS = {
17145
17891
  };
17146
17892
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
17147
17893
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
17894
+ var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
17148
17895
  var toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
17149
17896
  var eventOf = (name, value) => {
17150
17897
  const toolUseId = optionalText(value.tool_use_id);
@@ -17169,7 +17916,7 @@ var eventOf = (name, value) => {
17169
17916
  if (!SHELL_TOOLS.has(value.tool_name))
17170
17917
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
17171
17918
  const command = toolCommand(value);
17172
- return command ? { ok: true, event: { kind: "shell.pre", command, toolUseId } } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
17919
+ 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}` };
17173
17920
  }
17174
17921
  case "PostToolUse": {
17175
17922
  const command = toolCommand(value);
@@ -17183,7 +17930,8 @@ var eventOf = (name, value) => {
17183
17930
  command,
17184
17931
  stdout: optionalText(response.stdout) ?? "",
17185
17932
  exitCode: typeof response.exit_code === "number" ? response.exit_code : null,
17186
- toolUseId
17933
+ toolUseId,
17934
+ dialect: dialectOf2(value)
17187
17935
  }
17188
17936
  };
17189
17937
  }
@@ -17224,7 +17972,13 @@ var eventOf = (name, value) => {
17224
17972
  };
17225
17973
  }
17226
17974
  };
17227
- var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
17975
+ var CONTEXT_EVENTS = new Set([
17976
+ "SessionStart",
17977
+ "UserPromptSubmit",
17978
+ "SubagentStart",
17979
+ "PreToolUse",
17980
+ "PostToolUse"
17981
+ ]);
17228
17982
  var render = (decision, native) => {
17229
17983
  if (decision.kind === "deny" && native === "PreToolUse")
17230
17984
  return {
@@ -17306,7 +18060,7 @@ var CODEX_DESCRIPTOR = {
17306
18060
  "shell.pre": { support: "native", native: "PreToolUse" },
17307
18061
  "tool.pre": { support: "native", native: "PreToolUse" },
17308
18062
  "write.pre": { support: "undocumented", native: null },
17309
- "shell.post": undocumented,
18063
+ "shell.post": { support: "native", native: "PostToolUse" },
17310
18064
  "subagent.start": { support: "native", native: "SubagentStart" },
17311
18065
  "subagent.stop": { support: "native", native: "SubagentStop" },
17312
18066
  "prompt.submit": undocumented,
@@ -17327,7 +18081,7 @@ var CODEX_DESCRIPTOR = {
17327
18081
  worktreeIsolation: "undocumented",
17328
18082
  maxConcurrency: "undocumented"
17329
18083
  },
17330
- provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
18084
+ provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
17331
18085
  interaction: { questions: "none", writeBoundary: "partial" },
17332
18086
  stopControl: "undocumented",
17333
18087
  shellAvailable: "native",
@@ -17393,6 +18147,7 @@ var CODEX_DESCRIPTOR = {
17393
18147
  var EVENTS2 = {
17394
18148
  SessionStart: "session.start",
17395
18149
  PreToolUse: "shell.pre",
18150
+ PostToolUse: "shell.post",
17396
18151
  SubagentStart: "subagent.start",
17397
18152
  SubagentStop: "subagent.stop"
17398
18153
  };
@@ -17406,10 +18161,11 @@ var parseCodexHookInput = (value) => {
17406
18161
  const cwd = existingDirectory(value.cwd);
17407
18162
  if (!cwd)
17408
18163
  return { ok: false, error: "cwd must be an existing absolute directory" };
17409
- if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
18164
+ const toolEvent = event === "PreToolUse" || event === "PostToolUse";
18165
+ if (toolEvent && !nonEmpty2(value.tool_name))
17410
18166
  return { ok: false, error: "tool_name is required" };
17411
18167
  const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
17412
- if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
18168
+ if (toolEvent && isShellTool(value.tool_name) && !command)
17413
18169
  return { ok: false, error: "tool_input.command is required for shell tools" };
17414
18170
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
17415
18171
  return { ok: false, error: "agent_id is required" };
@@ -17425,7 +18181,8 @@ var parseCodexHookInput = (value) => {
17425
18181
  ...event === "SessionStart" ? { source: SOURCES2.has(String(value.source)) ? value.source : "resume" } : {},
17426
18182
  ...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
17427
18183
  ...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},
17428
- ...event === "PreToolUse" ? { tool_input: value.tool_input } : {},
18184
+ ...toolEvent ? { tool_input: value.tool_input } : {},
18185
+ ...event === "PostToolUse" && typeof value.tool_response === "string" ? { tool_response: value.tool_response } : {},
17429
18186
  ...command ? { command } : {},
17430
18187
  ...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
17431
18188
  ...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
@@ -17450,6 +18207,16 @@ var protocolEvent = (input) => {
17450
18207
  toolUseId
17451
18208
  } : { kind: "tool.pre", tool: input.tool_name, toolUseId };
17452
18209
  }
18210
+ case "PostToolUse": {
18211
+ const toolUseId = input.tool_use_id ?? null;
18212
+ return isShellTool(input.tool_name) ? {
18213
+ kind: "shell.post",
18214
+ command: input.command ?? "",
18215
+ stdout: input.tool_response ?? "",
18216
+ exitCode: null,
18217
+ toolUseId
18218
+ } : { kind: "tool.pre", tool: input.tool_name, toolUseId };
18219
+ }
17453
18220
  case "SubagentStart":
17454
18221
  return {
17455
18222
  kind: "subagent.start",
@@ -17514,11 +18281,11 @@ var codexAdapter = {
17514
18281
  };
17515
18282
  },
17516
18283
  render: render2,
17517
- 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>`
18284
+ 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>`
17518
18285
  };
17519
18286
 
17520
18287
  // packages/workit-core/src/hooks/hosts/cursor.ts
17521
- import path18 from "node:path";
18288
+ import path19 from "node:path";
17522
18289
  var undocumented2 = { support: "undocumented", native: null };
17523
18290
  var none = { support: "none", native: null };
17524
18291
  var CURSOR_DESCRIPTOR = {
@@ -17699,7 +18466,7 @@ var protocolEvent2 = (input) => {
17699
18466
  return { kind: "compact.pre", trigger: "auto" };
17700
18467
  }
17701
18468
  };
17702
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path18.sep) ? root : `${root}${path18.sep}`);
18469
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path19.sep) ? root : `${root}${path19.sep}`);
17703
18470
  var hookCwd = (input) => {
17704
18471
  const cwd = existingDirectory(input.cwd);
17705
18472
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -17722,6 +18489,8 @@ var render3 = (decision, native) => {
17722
18489
  json: cursorDeny(decision.reason),
17723
18490
  exitCode: native !== null && BLOCKING.has(native) ? 2 : 0
17724
18491
  };
18492
+ if (decision.kind === "context" && native === "beforeShellExecution")
18493
+ return { json: { permission: "allow", agent_message: decision.text }, exitCode: 0 };
17725
18494
  if (decision.kind === "context")
17726
18495
  return { json: { additional_context: decision.text }, exitCode: 0 };
17727
18496
  if (decision.kind === "notice")
@@ -17758,7 +18527,8 @@ var cursorAdapter = {
17758
18527
  }
17759
18528
  };
17760
18529
  },
17761
- render: render3
18530
+ render: render3,
18531
+ 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
17762
18532
  };
17763
18533
 
17764
18534
  // packages/workit-core/src/hooks/run.ts