@brainervirus/workit-cursor 8.1.1 → 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
 
@@ -10436,7 +10630,7 @@ var nonBlank, branchPreset, commitPreset, workspaceBranchPolicySchema, workspace
10436
10630
  profiles: record(nonBlank, profileSchema).optional()
10437
10631
  }).passthrough();
10438
10632
  return entrySchema;
10439
- }, runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
10633
+ }, resolveRuntimeWorkspaceVcs = (cwd) => resolveRuntimeWorkspaceCandidate(cwd, "vcs"), runtimeWorkspaceIndexSchema, matchRuntimeWorkspace = (cwd) => {
10440
10634
  const file = path6.join(configDir(), "workspaces.json");
10441
10635
  let raw;
10442
10636
  try {
@@ -13533,14 +13727,16 @@ var MIGRATION_PENDING = "workit migration pending", STORE_FORMAT = 3, STORE_MIN_
13533
13727
  heldInProcess.delete(lock);
13534
13728
  }, envActor = () => {
13535
13729
  const value = (key) => process.env[key]?.trim() || null;
13730
+ const acting = hostSessionFromEnv(process.env);
13536
13731
  return {
13537
- host: value("WORKIT_HOST") ?? "runtime",
13538
- session: value("WORKIT_SESSION_ID"),
13732
+ host: acting.host ?? "runtime",
13733
+ session: acting.session,
13539
13734
  agentId: value("WORKIT_AGENT_ID")
13540
13735
  };
13541
13736
  };
13542
13737
  var init_task_store = __esm(() => {
13543
13738
  init_file_lock();
13739
+ init_host_session();
13544
13740
  init_package_root();
13545
13741
  init_task_contract();
13546
13742
  init_store_lock();
@@ -16620,7 +16816,7 @@ var withReleaseTracks = (workspaceRoot, policy) => {
16620
16816
  error: `policy_unavailable: cannot validate branch ${JSON.stringify(name)} because ${error instanceof Error ? error.message : String(error)}; ${correction}`
16621
16817
  };
16622
16818
  }
16623
- };
16819
+ }, resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
16624
16820
  var init_branch = __esm(() => {
16625
16821
  init_branch_policy();
16626
16822
  init_commit_flavors();
@@ -16658,52 +16854,40 @@ var init_policy = __esm(() => {
16658
16854
  init_route_intent();
16659
16855
  });
16660
16856
 
16661
- // packages/workit-core/src/hooks/write-gate.ts
16662
- import { realpathSync as realpathSync5 } from "node:fs";
16663
- import path16 from "node:path";
16664
- function writeGate(cwd, paths) {
16665
- try {
16666
- const gate = gateFor(cwd);
16667
- if (!gate || gate.blockers.length === 0)
16668
- return NONE2;
16669
- if (paths && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
16670
- return NONE2;
16671
- const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
16672
- return {
16673
- kind: "deny",
16674
- reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
16675
- unblock: gate.blockers[0].reason
16676
- };
16677
- } catch {
16678
- return NONE2;
16679
- }
16680
- }
16681
- var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16682
- let rest = "";
16683
- for (let dir = path16.resolve(file);; dir = path16.dirname(dir)) {
16684
- try {
16685
- return path16.join(realpathSync5(dir), rest);
16686
- } catch {
16687
- if (path16.dirname(dir) === dir)
16688
- return path16.resolve(file);
16689
- 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;
16690
16870
  }
16871
+ if (dialect === "posix" && char === "\\" || dialect === "powershell" && char === "`") {
16872
+ index++;
16873
+ continue;
16874
+ }
16875
+ if (char === '"')
16876
+ return index;
16691
16877
  }
16692
- }, exempt = (cwd, root, file, plans) => {
16693
- const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
16694
- const relative = path16.relative(fold(canonical2(root)), fold(canonical2(path16.resolve(cwd, file))));
16695
- if (relative.startsWith("..") || path16.isAbsolute(relative))
16696
- return true;
16697
- const posix = relative.split(path16.sep).join("/");
16698
- const segments = posix.split("/");
16699
- return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
16700
- }, 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") => {
16701
16884
  const segments = [];
16702
16885
  let current = { words: [], redirects: [] };
16703
16886
  let word = "";
16704
16887
  let inWord = false;
16705
16888
  let pending = null;
16706
16889
  const heredocs = [];
16890
+ const escape = dialect === "posix" ? "\\" : "`";
16707
16891
  const flushWord = () => {
16708
16892
  if (!inWord)
16709
16893
  return;
@@ -16727,11 +16911,11 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16727
16911
  for (let index = 0;index < command.length; index++) {
16728
16912
  const char = command[index];
16729
16913
  if (char === "'" || char === '"') {
16730
- const end = command.indexOf(char, index + 1);
16731
- 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);
16732
16916
  inWord = true;
16733
16917
  index = end < 0 ? command.length : end;
16734
- } else if (char === "\\" && index + 1 < command.length) {
16918
+ } else if (char === escape && index + 1 < command.length) {
16735
16919
  word += command[++index];
16736
16920
  inWord = true;
16737
16921
  } else if (char === `
@@ -16748,7 +16932,14 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16748
16932
  }
16749
16933
  } else if (/\s/.test(char))
16750
16934
  flushWord();
16751
- 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)) {
16752
16943
  if (char === "&" && command[index + 1] === ">")
16753
16944
  continue;
16754
16945
  flushSegment();
@@ -16766,7 +16957,7 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16766
16957
  inWord = true;
16767
16958
  }
16768
16959
  pending = "redirect";
16769
- } else if (char === "<") {
16960
+ } else if (char === "<" && dialect === "posix") {
16770
16961
  flushWord();
16771
16962
  if (command[index + 1] === "<") {
16772
16963
  index++;
@@ -16782,6 +16973,47 @@ var NONE2, CASE_INSENSITIVE, canonical2 = (file) => {
16782
16973
  }
16783
16974
  flushSegment();
16784
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);
16785
17017
  }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
16786
17018
  let words = input;
16787
17019
  while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
@@ -16919,18 +17151,461 @@ var init_write_gate = __esm(() => {
16919
17151
  cache2 = new Map;
16920
17152
  });
16921
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
+
16922
17597
  // packages/workit-core/src/hooks/hosts/fields.ts
16923
17598
  import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
16924
- import path17 from "node:path";
17599
+ import path18 from "node:path";
16925
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) => {
16926
- if (!nonEmpty2(value) || !path17.isAbsolute(value) || !existsSync6(value))
17601
+ if (!nonEmpty2(value) || !path18.isAbsolute(value) || !existsSync6(value))
16927
17602
  return null;
16928
17603
  try {
16929
17604
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
16930
17605
  } catch {
16931
17606
  return null;
16932
17607
  }
16933
- }, 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) => {
16934
17609
  if (nonEmpty2(value))
16935
17610
  return value;
16936
17611
  if (!Array.isArray(value) || value.length === 0)
@@ -16939,9 +17614,9 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
16939
17614
  if (!argv.every((arg) => typeof arg === "string"))
16940
17615
  return null;
16941
17616
  const shell = argv[0].split(/[\\/]/).at(-1)?.replace(/\.exe$/i, "").toLowerCase() ?? "";
16942
- if (SHELLS.has(shell))
17617
+ if (SHELLS2.has(shell))
16943
17618
  for (let index = 1;index < argv.length - 1; index++) {
16944
- if (SCRIPT_FLAG.test(argv[index]))
17619
+ if (SCRIPT_FLAG2.test(argv[index]))
16945
17620
  return nonEmpty2(argv[index + 1]) ? argv[index + 1] : null;
16946
17621
  if (!/^[-/]/.test(argv[index]))
16947
17622
  break;
@@ -16959,8 +17634,8 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
16959
17634
  return [...new Set(paths)];
16960
17635
  };
16961
17636
  var init_fields = __esm(() => {
16962
- SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
16963
- 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;
16964
17639
  WRITE_TOOLS = new Set([
16965
17640
  "write",
16966
17641
  "edit",
@@ -16990,7 +17665,7 @@ function handleHook(input, deps) {
16990
17665
  switch (event.kind) {
16991
17666
  case "session.start": {
16992
17667
  if (!usable2(descriptor.context.sessionStart))
16993
- return NONE3;
17668
+ return NONE4;
16994
17669
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
16995
17670
  const offer = event.source === "startup" && (key === null || !offered.has(key));
16996
17671
  if (offer && key !== null)
@@ -17005,19 +17680,34 @@ function handleHook(input, deps) {
17005
17680
  }
17006
17681
  case "context.turn": {
17007
17682
  if (!usable2(descriptor.context.perTurn))
17008
- return NONE3;
17683
+ return NONE4;
17009
17684
  const text = turnContextText(input, descriptor);
17010
- return text ? { kind: "context", text } : NONE3;
17685
+ return text ? { kind: "context", text } : NONE4;
17011
17686
  }
17012
17687
  case "shell.pre": {
17013
- const policy = usable2(descriptor.shellPolicy.deny) ? shellPolicy(input.cwd, event.command) : NONE3;
17014
- 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")
17015
17691
  return policy;
17016
- const writes = shellWrites(event.command);
17017
- 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;
17018
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;
17019
17709
  case "write.pre":
17020
- 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;
17021
17711
  case "subagent.start":
17022
17712
  return {
17023
17713
  kind: "context",
@@ -17029,14 +17719,13 @@ function handleHook(input, deps) {
17029
17719
  userMessage: "Workit context may be stale after compaction; re-run inspection or resume before acting."
17030
17720
  };
17031
17721
  case "tool.pre":
17032
- case "shell.post":
17033
17722
  case "subagent.stop":
17034
17723
  case "prompt.submit":
17035
17724
  case "stop":
17036
- return NONE3;
17725
+ return NONE4;
17037
17726
  }
17038
17727
  }
17039
- 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) => {
17040
17729
  if (host === "claude_code" && event.agentType === CLAUDE_WORKTREE_IMPLEMENTER)
17041
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.`;
17042
17731
  const readOnly = `Workit observed ${descriptor.label} subagent ${event.agentId} (${event.agentType}) as read-only/agent-guided.`;
@@ -17045,10 +17734,10 @@ var usable2 = (value) => value === "native" || value === "partial", NONE3, offer
17045
17734
  return readOnly;
17046
17735
  }, failureDecision = (descriptor, event, error) => {
17047
17736
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
17048
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE3;
17737
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE4;
17049
17738
  if (event === "session.start" || event === "subagent.start")
17050
17739
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
17051
- return NONE3;
17740
+ return NONE4;
17052
17741
  }, dispatchHook = (adapter, raw, env = process.env) => {
17053
17742
  const parsed = adapter.parse(raw, env);
17054
17743
  if (!parsed.ok)
@@ -17074,14 +17763,15 @@ var usable2 = (value) => value === "native" || value === "partial", NONE3, offer
17074
17763
  var init_handle = __esm(() => {
17075
17764
  init_context();
17076
17765
  init_policy();
17766
+ init_raw_git();
17077
17767
  init_write_gate();
17078
- NONE3 = { kind: "none" };
17768
+ NONE4 = { kind: "none" };
17079
17769
  offered = new Set;
17080
17770
  CLAUDE_JUDGES = new Set(["workit:verifier", "workit:reviewer"]);
17081
17771
  });
17082
17772
 
17083
17773
  // packages/workit-core/src/hooks/hosts/claude-code.ts
17084
- 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) => {
17085
17775
  const toolUseId = optionalText(value.tool_use_id);
17086
17776
  switch (name) {
17087
17777
  case "SessionStart":
@@ -17104,7 +17794,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
17104
17794
  if (!SHELL_TOOLS.has(value.tool_name))
17105
17795
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
17106
17796
  const command = toolCommand(value);
17107
- 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}` };
17108
17798
  }
17109
17799
  case "PostToolUse": {
17110
17800
  const command = toolCommand(value);
@@ -17118,7 +17808,8 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
17118
17808
  command,
17119
17809
  stdout: optionalText(response.stdout) ?? "",
17120
17810
  exitCode: typeof response.exit_code === "number" ? response.exit_code : null,
17121
- toolUseId
17811
+ toolUseId,
17812
+ dialect: dialectOf2(value)
17122
17813
  }
17123
17814
  };
17124
17815
  }
@@ -17281,7 +17972,13 @@ var init_claude_code = __esm(() => {
17281
17972
  };
17282
17973
  SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
17283
17974
  SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
17284
- CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
17975
+ CONTEXT_EVENTS = new Set([
17976
+ "SessionStart",
17977
+ "UserPromptSubmit",
17978
+ "SubagentStart",
17979
+ "PreToolUse",
17980
+ "PostToolUse"
17981
+ ]);
17285
17982
  claudeCodeAdapter = {
17286
17983
  descriptor: CLAUDE_CODE_DESCRIPTOR,
17287
17984
  parse(raw) {
@@ -17335,10 +18032,11 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17335
18032
  const cwd = existingDirectory(value.cwd);
17336
18033
  if (!cwd)
17337
18034
  return { ok: false, error: "cwd must be an existing absolute directory" };
17338
- if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
18035
+ const toolEvent = event === "PreToolUse" || event === "PostToolUse";
18036
+ if (toolEvent && !nonEmpty2(value.tool_name))
17339
18037
  return { ok: false, error: "tool_name is required" };
17340
18038
  const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
17341
- if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
18039
+ if (toolEvent && isShellTool(value.tool_name) && !command)
17342
18040
  return { ok: false, error: "tool_input.command is required for shell tools" };
17343
18041
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
17344
18042
  return { ok: false, error: "agent_id is required" };
@@ -17354,7 +18052,8 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17354
18052
  ...event === "SessionStart" ? { source: SOURCES2.has(String(value.source)) ? value.source : "resume" } : {},
17355
18053
  ...nonEmpty2(value.turn_id) ? { turn_id: value.turn_id } : {},
17356
18054
  ...nonEmpty2(value.tool_name) ? { tool_name: value.tool_name } : {},
17357
- ...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 } : {},
17358
18057
  ...command ? { command } : {},
17359
18058
  ...nonEmpty2(value.tool_use_id) ? { tool_use_id: value.tool_use_id } : {},
17360
18059
  ...nonEmpty2(value.agent_id) ? { agent_id: value.agent_id } : {},
@@ -17378,6 +18077,16 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
17378
18077
  toolUseId
17379
18078
  } : { kind: "tool.pre", tool: input.tool_name, toolUseId };
17380
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
+ }
17381
18090
  case "SubagentStart":
17382
18091
  return {
17383
18092
  kind: "subagent.start",
@@ -17426,7 +18135,7 @@ var init_codex = __esm(() => {
17426
18135
  "shell.pre": { support: "native", native: "PreToolUse" },
17427
18136
  "tool.pre": { support: "native", native: "PreToolUse" },
17428
18137
  "write.pre": { support: "undocumented", native: null },
17429
- "shell.post": undocumented,
18138
+ "shell.post": { support: "native", native: "PostToolUse" },
17430
18139
  "subagent.start": { support: "native", native: "SubagentStart" },
17431
18140
  "subagent.stop": { support: "native", native: "SubagentStop" },
17432
18141
  "prompt.submit": undocumented,
@@ -17447,7 +18156,7 @@ var init_codex = __esm(() => {
17447
18156
  worktreeIsolation: "undocumented",
17448
18157
  maxConcurrency: "undocumented"
17449
18158
  },
17450
- provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
18159
+ provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "native" },
17451
18160
  interaction: { questions: "none", writeBoundary: "partial" },
17452
18161
  stopControl: "undocumented",
17453
18162
  shellAvailable: "native",
@@ -17513,6 +18222,7 @@ var init_codex = __esm(() => {
17513
18222
  EVENTS2 = {
17514
18223
  SessionStart: "session.start",
17515
18224
  PreToolUse: "shell.pre",
18225
+ PostToolUse: "shell.post",
17516
18226
  SubagentStart: "subagent.start",
17517
18227
  SubagentStop: "subagent.stop"
17518
18228
  };
@@ -17547,12 +18257,12 @@ var init_codex = __esm(() => {
17547
18257
  };
17548
18258
  },
17549
18259
  render: render2,
17550
- 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>`
17551
18261
  };
17552
18262
  });
17553
18263
 
17554
18264
  // packages/workit-core/src/hooks/hosts/cursor.ts
17555
- import path18 from "node:path";
18265
+ import path19 from "node:path";
17556
18266
  var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
17557
18267
  if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
17558
18268
  return { ok: false, error: "hook_event_name is required" };
@@ -17621,7 +18331,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
17621
18331
  case "preCompact":
17622
18332
  return { kind: "compact.pre", trigger: "auto" };
17623
18333
  }
17624
- }, 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) => {
17625
18335
  const cwd = existingDirectory(input.cwd);
17626
18336
  if (cwd && input.hook_event_name === "beforeShellExecution")
17627
18337
  return cwd;
@@ -17639,6 +18349,8 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
17639
18349
  json: cursorDeny(decision.reason),
17640
18350
  exitCode: native !== null && BLOCKING.has(native) ? 2 : 0
17641
18351
  };
18352
+ if (decision.kind === "context" && native === "beforeShellExecution")
18353
+ return { json: { permission: "allow", agent_message: decision.text }, exitCode: 0 };
17642
18354
  if (decision.kind === "context")
17643
18355
  return { json: { additional_context: decision.text }, exitCode: 0 };
17644
18356
  if (decision.kind === "notice")
@@ -17788,7 +18500,8 @@ var init_cursor = __esm(() => {
17788
18500
  }
17789
18501
  };
17790
18502
  },
17791
- 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
17792
18505
  };
17793
18506
  });
17794
18507
 
@@ -17838,6 +18551,7 @@ var init_hooks = __esm(async () => {
17838
18551
  init_context();
17839
18552
  init_policy();
17840
18553
  init_write_gate();
18554
+ init_raw_git();
17841
18555
  init_fields();
17842
18556
  init_handle();
17843
18557
  init_claude_code();