@brainervirus/workit-cursor 8.22.3 → 8.23.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.
@@ -17953,6 +17953,24 @@ function workitInvocations(command, dialect = "posix", depth = 0) {
17953
17953
  }
17954
17954
  return found;
17955
17955
  }
17956
+ var WORKIT_GLOBAL = /^--(?:cwd|session|token|as)(=|$)/;
17957
+ function skipWorkitGlobals(args) {
17958
+ let cwd = null;
17959
+ let at = 0;
17960
+ for (;at < args.length; at++) {
17961
+ const match = WORKIT_GLOBAL.exec(args[at]);
17962
+ if (match?.[1] === "=") {
17963
+ if (args[at].startsWith("--cwd="))
17964
+ cwd = args[at].slice(6);
17965
+ } else if (match && args[at + 1] !== undefined) {
17966
+ if (args[at] === "--cwd")
17967
+ cwd = args[at + 1];
17968
+ at++;
17969
+ } else if (args[at] !== "--json")
17970
+ break;
17971
+ }
17972
+ return { rest: args.slice(at), cwd };
17973
+ }
17956
17974
  var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
17957
17975
  var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
17958
17976
  var pushDestination = (destination) => {
@@ -19774,7 +19792,7 @@ var splitDenial = (plan) => ({
19774
19792
  unblock: plan.unblock
19775
19793
  });
19776
19794
  // packages/workit-core/src/hooks/write-gate.ts
19777
- import { realpathSync as realpathSync5 } from "node:fs";
19795
+ import { existsSync as existsSync7, realpathSync as realpathSync5 } from "node:fs";
19778
19796
  import os10 from "node:os";
19779
19797
  import path27 from "node:path";
19780
19798
 
@@ -20003,7 +20021,7 @@ var exempt = (cwd, root, file, plans) => {
20003
20021
  const segments = posix.split("/");
20004
20022
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20005
20023
  };
20006
- var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20024
+ var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs", "busybox"]);
20007
20025
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
20008
20026
  var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
20009
20027
  var wrappedScript = (input) => {
@@ -20015,9 +20033,9 @@ var wrappedScript = (input) => {
20015
20033
  return null;
20016
20034
  if ("script" in tail)
20017
20035
  return tail.script;
20018
- const [shell, flag, script] = tail.argv;
20019
- if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
20020
- return script ?? null;
20036
+ const script = shellScript2(tail.argv);
20037
+ if (SCRIPT_SHELLS.has((tail.argv[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && script)
20038
+ return script;
20021
20039
  return tail.argv.map(quoteWord2).join(" ");
20022
20040
  };
20023
20041
  var editsOf = (input) => {
@@ -20096,6 +20114,94 @@ var editsOf = (input) => {
20096
20114
  return [];
20097
20115
  }
20098
20116
  };
20117
+ var shellScript2 = (words) => {
20118
+ if (!SCRIPT_SHELLS.has((words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase()))
20119
+ return null;
20120
+ for (let index = 1;index < words.length; index++) {
20121
+ const word = words[index];
20122
+ if (/^-[a-z]*c$/.test(word))
20123
+ return words[index + 1] ?? null;
20124
+ if (/^[-+][oO]$/.test(word))
20125
+ index++;
20126
+ else if (!/^[-+]/.test(word))
20127
+ return null;
20128
+ }
20129
+ return null;
20130
+ };
20131
+ var XARGS_VALUE = /^-[IinLPdsEa]$/;
20132
+ var runsWriter = (words) => {
20133
+ const filled = [...words.filter((word) => word !== "{}" && word !== ";" && word !== "+"), "x"];
20134
+ const edits = editsOf(filled);
20135
+ return edits === null || edits.length > 0 || opaqueWrites(filled) !== null;
20136
+ };
20137
+ var opaqueWrites = (input) => {
20138
+ let words = input;
20139
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0]))) {
20140
+ if (words[0] === "xargs") {
20141
+ let at = 1;
20142
+ while (words[at]?.startsWith("-"))
20143
+ at += XARGS_VALUE.test(words[at]) ? 2 : 1;
20144
+ return runsWriter(words.slice(at)) ? ["."] : null;
20145
+ }
20146
+ words = words.slice(1);
20147
+ }
20148
+ const [verb, ...args] = words;
20149
+ const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20150
+ const files = operands(args);
20151
+ const has = (pattern) => args.some((arg) => pattern.test(arg));
20152
+ const listed = (named) => named.length ? named : ["."];
20153
+ if (name === "unlink" || name === "shred")
20154
+ return listed(files);
20155
+ if (/^(?:chmod|chown|chgrp|chattr)$/.test(name))
20156
+ return listed(has(/^--reference/) ? files : files.slice(1));
20157
+ if (name === "rsync") {
20158
+ const dest = files.at(-1);
20159
+ const remote = /^[\w.@-]*:/.test(dest ?? "") && !/^[A-Za-z]:[\\/]/.test(dest ?? "");
20160
+ return dest === undefined ? ["."] : remote ? [] : [dest];
20161
+ }
20162
+ if (name === "find") {
20163
+ const exec = args.findIndex((arg) => /^-(?:exec|execdir|ok|okdir)$/.test(arg));
20164
+ const writes = has(/^-(?:delete|fprint\w*|fls)$/) || exec >= 0 && runsWriter(args.slice(exec + 1));
20165
+ const end = args.findIndex((arg) => /^[-(!]/.test(arg));
20166
+ return writes ? listed(end < 0 ? args : args.slice(0, end)) : null;
20167
+ }
20168
+ if (/^g?awk$/.test(name)) {
20169
+ if (!has(/^-i|^--include/))
20170
+ return null;
20171
+ const named = [];
20172
+ let program = !has(/^-f$|^--file/);
20173
+ for (let index = 0;index < args.length; index++)
20174
+ if (/^(?:-[ifvF]|--(?:include|file|assign|field-separator))$/.test(args[index]))
20175
+ index++;
20176
+ else if (args[index].startsWith("-"))
20177
+ continue;
20178
+ else if (program)
20179
+ program = false;
20180
+ else
20181
+ named.push(args[index]);
20182
+ return listed(named);
20183
+ }
20184
+ if (name === "perl")
20185
+ return has(/^-[a-zA-Z]*[eE]/) ? ["."] : null;
20186
+ if (/^(?:python[\d.]*|node|bun|deno|ruby|php)$/.test(name))
20187
+ return has(/^-[a-zA-Z]*[ce]$|^--eval$|^-p$/) ? ["."] : null;
20188
+ return shellScript2(words) === null ? null : ["."];
20189
+ };
20190
+ var linkSources = (args, at) => {
20191
+ const names = operands([...args]);
20192
+ const into = args.indexOf("-t");
20193
+ const dest = into >= 0 ? args[into + 1] : names.length > 1 ? names.at(-1) : ".";
20194
+ const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
20195
+ const target = dest === undefined ? null : at(dest);
20196
+ const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
20197
+ const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync7(target) || /[\\/]$/.test(dest ?? "") ? target : path27.dirname(target);
20198
+ return sources.flatMap((source) => {
20199
+ if (symbolic && base !== null && !path27.isAbsolute(homeExpanded(source)))
20200
+ return [path27.resolve(base, source)];
20201
+ const resolved = at(source);
20202
+ return resolved ? [resolved] : [];
20203
+ });
20204
+ };
20099
20205
  var shellWrites = (command, depth = 0) => {
20100
20206
  const targets = [];
20101
20207
  let unknown = false;
@@ -20205,11 +20311,15 @@ var gitMutationDirs = (cwd, command, dialect) => {
20205
20311
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20206
20312
  var PUSHD2 = new Set(["pushd", "push-location"]);
20207
20313
  var POPD2 = new Set(["popd", "pop-location"]);
20208
- var cdInto = (dir, words) => {
20314
+ var UNKNOWN_DIR = path27.resolve(path27.parse(process.cwd()).root, ".workit-unknown-dir");
20315
+ var cdInto = (dir, words, strict = false) => {
20316
+ const lost = strict ? UNKNOWN_DIR : undefined;
20209
20317
  const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20210
20318
  const target = named === undefined ? undefined : homeExpanded(named);
20211
- if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20212
- return;
20319
+ if (target === "-" || target && /^~|[$`*?]/.test(target))
20320
+ return lost;
20321
+ if (dir === undefined)
20322
+ return lost;
20213
20323
  return target === undefined ? os10.homedir() : path27.resolve(dir, target);
20214
20324
  };
20215
20325
  var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
@@ -20217,7 +20327,7 @@ var homeExpanded = (file) => {
20217
20327
  const match = /^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/.exec(file);
20218
20328
  return match ? (process.env.HOME || os10.homedir()) + file.slice(match[0].length) : file;
20219
20329
  };
20220
- var segmentTargets = (cwd, command, dialect, depth = 0) => {
20330
+ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
20221
20331
  const targets = [];
20222
20332
  let dir = cwd;
20223
20333
  let stack = [];
@@ -20241,21 +20351,35 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
20241
20351
  const words = lead < 0 ? [] : segment.words.slice(lead);
20242
20352
  const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20243
20353
  if (CD2.has(name)) {
20244
- dir = cdInto(dir, words);
20354
+ dir = cdInto(dir, words, strict);
20245
20355
  continue;
20246
20356
  }
20247
20357
  if (PUSHD2.has(name)) {
20248
20358
  stack.push(dir);
20249
- dir = cdInto(dir, words);
20359
+ dir = cdInto(dir, words, strict);
20250
20360
  continue;
20251
20361
  }
20252
20362
  if (POPD2.has(name)) {
20253
- dir = stack.length ? stack.pop() : undefined;
20363
+ dir = stack.length ? stack.pop() : strict ? UNKNOWN_DIR : undefined;
20254
20364
  continue;
20255
20365
  }
20256
- const inner = depth < 4 ? wrappedScript(words) : null;
20366
+ const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
20257
20367
  if (inner !== null && dir !== undefined)
20258
- targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20368
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
20369
+ if (strict && dir !== undefined) {
20370
+ const runIn = dir;
20371
+ for (const sub of depth < 4 ? segment.subs ?? [] : [])
20372
+ targets.push(...segmentTargets(runIn, sub, dialect, depth + 1, true));
20373
+ const opaque = segment.unanalyzable ? ["."] : inner === null ? opaqueWrites(words) : null;
20374
+ for (const file of opaque ?? []) {
20375
+ const resolved = at(file);
20376
+ if (resolved)
20377
+ targets.push(file === "." ? path27.join(resolved, DIR_TARGET) : resolved);
20378
+ }
20379
+ const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
20380
+ if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
20381
+ targets.push(...linkSources(words.slice(ln + 1), at));
20382
+ }
20259
20383
  for (const file of segment.redirects)
20260
20384
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20261
20385
  const resolved = at(file);
@@ -20272,11 +20396,29 @@ var segmentTargets = (cwd, command, dialect, depth = 0) => {
20272
20396
  if (resolved)
20273
20397
  targets.push(resolved);
20274
20398
  }
20275
- if (dir !== undefined && workitInvocations(segment.words.map(quoteWord2).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
20276
- targets.push(path27.join(dir, DIR_TARGET));
20399
+ if (dir !== undefined)
20400
+ for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
20401
+ const { at: runsIn, args } = workitCwd(dir, invocation);
20402
+ if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
20403
+ targets.push(path27.join(runsIn, DIR_TARGET));
20404
+ }
20277
20405
  }
20278
20406
  return targets;
20279
20407
  };
20408
+ var workitCwd = (dir, args) => {
20409
+ const { rest, cwd } = skipWorkitGlobals(args);
20410
+ return { at: cwd === null ? dir : path27.resolve(dir, homeExpanded(cwd)), args: rest };
20411
+ };
20412
+ var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
20413
+ try {
20414
+ return [
20415
+ ...segmentTargets(cwd, command, dialect, 0, strict),
20416
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
20417
+ ];
20418
+ } catch {
20419
+ return [];
20420
+ }
20421
+ };
20280
20422
  var overridden = (command, dialect) => {
20281
20423
  const segments = segmentsOf(command, dialect);
20282
20424
  if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
@@ -20382,16 +20524,8 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20382
20524
  } catch {}
20383
20525
  const writes = shellWrites(command);
20384
20526
  let targets = [];
20385
- if (owner) {
20386
- try {
20387
- targets = [
20388
- ...segmentTargets(cwd, command, dialect),
20389
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
20390
- ];
20391
- } catch {
20392
- targets = [];
20393
- }
20394
- }
20527
+ if (owner)
20528
+ targets = shellMutationTargets(cwd, command, dialect);
20395
20529
  if (owner && targets.length) {
20396
20530
  const live = liveGate(owner, targets, deps);
20397
20531
  if (live.kind !== "none") {
@@ -20858,13 +20992,13 @@ ${line}` };
20858
20992
  return decision;
20859
20993
  };
20860
20994
  // packages/workit-core/src/hooks/hosts/fields.ts
20861
- import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20995
+ import { existsSync as existsSync8, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20862
20996
  import path28 from "node:path";
20863
20997
  var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20864
20998
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20865
20999
  var optionalText = (value) => typeof value === "string" ? value : null;
20866
21000
  var existingDirectory = (value) => {
20867
- if (!nonEmpty2(value) || !path28.isAbsolute(value) || !existsSync7(value))
21001
+ if (!nonEmpty2(value) || !path28.isAbsolute(value) || !existsSync8(value))
20868
21002
  return null;
20869
21003
  try {
20870
21004
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -21067,9 +21201,266 @@ var cwdRewrite = (command) => {
21067
21201
  return `workit --cwd ${dir} ${rest.slice("workit".length).trimStart()}`.trimEnd();
21068
21202
  };
21069
21203
 
21204
+ // packages/workit-core/src/hooks/worktree-confine.ts
21205
+ import { createHash as createHash17 } from "node:crypto";
21206
+ import fs23 from "node:fs";
21207
+ import path30 from "node:path";
21208
+ var FANOUT_TOKEN_FILE = "workit-fanout-token";
21209
+ var NONE5 = { kind: "none" };
21210
+ var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
21211
+ var canonical3 = (file) => {
21212
+ let rest = "";
21213
+ for (let dir = path30.resolve(file);; dir = path30.dirname(dir)) {
21214
+ try {
21215
+ return path30.join(fs23.realpathSync.native(dir), rest);
21216
+ } catch {
21217
+ if (path30.dirname(dir) === dir)
21218
+ return path30.resolve(file);
21219
+ rest = rest ? path30.join(path30.basename(dir), rest) : path30.basename(dir);
21220
+ }
21221
+ }
21222
+ };
21223
+ var within = (child, parent) => {
21224
+ const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
21225
+ const relative = path30.relative(fold(parent), fold(child));
21226
+ return relative === "" || !relative.startsWith("..") && !path30.isAbsolute(relative);
21227
+ };
21228
+ var checkoutOf2 = (file) => {
21229
+ for (let dir = canonical3(file);; dir = path30.dirname(dir)) {
21230
+ const dotGit = path30.join(dir, ".git");
21231
+ if (fs23.existsSync(dotGit)) {
21232
+ if (!fs23.statSync(dotGit).isFile())
21233
+ return { root: dir, workit: false };
21234
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
21235
+ return {
21236
+ root: dir,
21237
+ workit: Boolean(admin) && fs23.existsSync(path30.join(path30.resolve(dir, admin), FANOUT_TOKEN_FILE))
21238
+ };
21239
+ }
21240
+ if (path30.dirname(dir) === dir)
21241
+ return null;
21242
+ }
21243
+ };
21244
+ var workitWorktreeOf = (file) => {
21245
+ try {
21246
+ const checkout = checkoutOf2(file);
21247
+ return checkout?.workit ? checkout.root : null;
21248
+ } catch {
21249
+ return null;
21250
+ }
21251
+ };
21252
+ var bindingDir = () => path30.join(resolveStateDir(), "worktree-bind");
21253
+ var bindingFile = (session, agentId) => path30.join(bindingDir(), `${createHash17("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
21254
+ var readBinding = (file) => {
21255
+ try {
21256
+ const value = JSON.parse(fs23.readFileSync(file, "utf8"));
21257
+ return typeof value.worktree === "string" ? value : null;
21258
+ } catch {
21259
+ return null;
21260
+ }
21261
+ };
21262
+ var boundWorktree = (input) => {
21263
+ const agentId = input.session.agentId;
21264
+ if (!agentId)
21265
+ return null;
21266
+ const binding = readBinding(bindingFile(input.session.id, agentId));
21267
+ return binding && workitWorktreeOf(binding.worktree) === binding.worktree ? binding.worktree : null;
21268
+ };
21269
+ var bind = (input, worktree) => {
21270
+ const agentId = input.session.agentId;
21271
+ const file = bindingFile(input.session.id, agentId);
21272
+ const binding = {
21273
+ worktree,
21274
+ session: input.session.id,
21275
+ agentId,
21276
+ at: new Date().toISOString()
21277
+ };
21278
+ fs23.mkdirSync(path30.dirname(file), { recursive: true });
21279
+ fs23.writeFileSync(file, JSON.stringify(binding));
21280
+ try {
21281
+ if (inWorkitWorkspace(input.cwd))
21282
+ appendHookObserved(input.cwd, {
21283
+ type: "worktree.bound",
21284
+ actor: { host: input.host, session: input.session.id, agentId },
21285
+ agentType: input.session.agentType,
21286
+ worktree
21287
+ });
21288
+ } catch {}
21289
+ };
21290
+ var hostIsolated = (cwd, agentId) => {
21291
+ try {
21292
+ if (agentId === null)
21293
+ return false;
21294
+ const checkout = checkoutOf2(cwd);
21295
+ if (!checkout)
21296
+ return false;
21297
+ const dotGit = path30.join(checkout.root, ".git");
21298
+ if (!fs23.statSync(dotGit).isFile())
21299
+ return false;
21300
+ const admin = /^gitdir:\s*(.+)$/mu.exec(fs23.readFileSync(dotGit, "utf8"))?.[1]?.trim();
21301
+ if (!admin)
21302
+ return false;
21303
+ const adminDir = path30.resolve(checkout.root, admin);
21304
+ const back = fs23.readFileSync(path30.join(adminDir, "gitdir"), "utf8").trim();
21305
+ if (path30.relative(canonical3(path30.resolve(adminDir, back)), canonical3(dotGit)) !== "")
21306
+ return false;
21307
+ const common = path30.join(adminDir, "commondir");
21308
+ const gitDir = fs23.existsSync(common) ? path30.resolve(adminDir, fs23.readFileSync(common, "utf8").trim()) : adminDir;
21309
+ const expected = path30.join(path30.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
21310
+ return path30.relative(canonical3(expected), checkout.root) === "";
21311
+ } catch {
21312
+ return false;
21313
+ }
21314
+ };
21315
+ var shown = (target) => path30.basename(target) === ".workit-live-target" ? path30.dirname(target) : target;
21316
+ var unboundDeny = (input, target) => {
21317
+ const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree> git commit -m … -- <paths>`, `workit --cwd <worktree> check <name>`). If your brief names no worktree, stop and report that to the lead.";
21318
+ return {
21319
+ kind: "deny",
21320
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
21321
+ unblock
21322
+ };
21323
+ };
21324
+ var outsideDeny = (input, worktree, target) => {
21325
+ const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree} git commit -m … -- <paths>\`, \`workit --cwd ${worktree} check <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
21326
+ return {
21327
+ kind: "deny",
21328
+ reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
21329
+ unblock
21330
+ };
21331
+ };
21332
+ var unknownDirDeny = (input, worktree) => {
21333
+ const wt = worktree ?? "<worktree>";
21334
+ const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
21335
+ return {
21336
+ kind: "deny",
21337
+ reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
21338
+ unblock
21339
+ };
21340
+ };
21341
+ function confineImplementer(input, targets) {
21342
+ try {
21343
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21344
+ return NONE5;
21345
+ if (targets.some((target) => within(path30.resolve(target), UNKNOWN_DIR)))
21346
+ return unknownDirDeny(input, boundWorktree(input));
21347
+ const bindings = canonical3(bindingDir());
21348
+ const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
21349
+ if (judged.length === 0)
21350
+ return NONE5;
21351
+ let worktree = boundWorktree(input);
21352
+ if (!worktree) {
21353
+ for (const { real } of judged) {
21354
+ worktree = within(real, bindings) ? null : workitWorktreeOf(real);
21355
+ if (worktree)
21356
+ break;
21357
+ }
21358
+ if (!worktree)
21359
+ return unboundDeny(input, judged[0].named);
21360
+ bind(input, worktree);
21361
+ }
21362
+ const root = canonical3(worktree);
21363
+ const outside = judged.find(({ real }) => !within(real, root) || within(real, bindings));
21364
+ return outside ? outsideDeny(input, worktree, outside.named) : NONE5;
21365
+ } catch {
21366
+ return NONE5;
21367
+ }
21368
+ }
21369
+ var MUTATING_REMOTE = new Set([
21370
+ "add",
21371
+ "remove",
21372
+ "rm",
21373
+ "rename",
21374
+ "set-url",
21375
+ "set-head",
21376
+ "set-branches",
21377
+ "prune"
21378
+ ]);
21379
+ var CONFIG_READ = /^(?:--get(?:-all|-regexp|-urlmatch)?|--list|-l|get|list)$/;
21380
+ var CONFIG_WRITE = /^(?:--(?:unset(?:-all)?|add|replace-all|rename-section|remove-section|edit)|-e|set|unset|rename-section|remove-section|edit)$/;
21381
+ var CONFIG_VALUE = /^(?:--file|-f|--blob|--type|--default|--comment)$/;
21382
+ var leadOwnedGit = (args) => {
21383
+ const [sub = "", ...rest] = args;
21384
+ if (rest.some((arg) => arg === "--help" || arg === "-h"))
21385
+ return false;
21386
+ const names = rest.filter((arg) => !arg.startsWith("-"));
21387
+ const flag = (pattern) => rest.some((arg) => pattern.test(arg));
21388
+ switch (sub) {
21389
+ case "worktree":
21390
+ return names[0] !== undefined && names[0] !== "list";
21391
+ case "update-ref":
21392
+ return true;
21393
+ case "symbolic-ref":
21394
+ return names.length > 1 || flag(/^(?:-d|--delete)$/);
21395
+ case "branch":
21396
+ return flag(/^-[a-zA-Z]*[dDfmMcC]|^--(?:delete|force|move|copy)$/);
21397
+ case "tag":
21398
+ return flag(/^-[a-zA-Z]*[df]|^--(?:delete|force)$/);
21399
+ case "push": {
21400
+ if (flag(/^(?:-d|--delete|--mirror|--prune)$/))
21401
+ return true;
21402
+ const refspecs = names.slice(1);
21403
+ return refspecs.some((name) => name.includes(":") || name.startsWith("+")) || refspecs.length > 0 && flag(/^-[a-zA-Z]*f|^--force/);
21404
+ }
21405
+ case "replace":
21406
+ case "filter-branch":
21407
+ case "gc":
21408
+ case "prune":
21409
+ case "repack":
21410
+ case "sparse-checkout":
21411
+ return true;
21412
+ case "notes":
21413
+ case "reflog":
21414
+ case "submodule":
21415
+ return !/^(?:list|show|status|exists)$/.test(names[0] ?? "list");
21416
+ case "stash":
21417
+ return names[0] === "drop" || names[0] === "clear";
21418
+ case "remote":
21419
+ return MUTATING_REMOTE.has(names[0] ?? "");
21420
+ case "config": {
21421
+ if (rest.some((arg) => CONFIG_READ.test(arg)))
21422
+ return false;
21423
+ if (rest.some((arg) => CONFIG_WRITE.test(arg)))
21424
+ return true;
21425
+ let operands = 0;
21426
+ for (let index = 0;index < rest.length; index++)
21427
+ if (CONFIG_VALUE.test(rest[index]))
21428
+ index++;
21429
+ else if (!rest[index].startsWith("-"))
21430
+ operands++;
21431
+ return operands > 1;
21432
+ }
21433
+ default:
21434
+ return false;
21435
+ }
21436
+ };
21437
+ var leadOwnedWorkit = (args) => !args.includes("--help") && args.some((_, start) => {
21438
+ if (start > 0 && !/(?:^|[\\/])workit(?:\.exe|\.cmd)?$/.test(args[start - 1]))
21439
+ return false;
21440
+ const [verb, sub] = skipWorkitGlobals(args.slice(start)).rest;
21441
+ return verb === "fanout" && /^(?:plan|worktree)$/.test(sub ?? "");
21442
+ });
21443
+ var leadOwnedDeny = (input, command) => {
21444
+ const unblock = "Worktrees, branches other than your own, refs, tags, remotes, git config, the shared stash and the fan-out plan belong to the lead: report what you need changed as a follow-up instead.";
21445
+ return {
21446
+ kind: "deny",
21447
+ reason: `workit worktree confinement: implementer ${input.session.agentId} may not run \`${command.slice(0, 160)}\`. ${unblock}`,
21448
+ unblock
21449
+ };
21450
+ };
21451
+ function confineImplementerShell(input, command, dialect = "posix") {
21452
+ try {
21453
+ if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
21454
+ return NONE5;
21455
+ if (rawInvocations(command, dialect).some((invocation) => invocation.tool === "git" && leadOwnedGit(invocation.args)) || workitInvocations(command, dialect).some(leadOwnedWorkit))
21456
+ return leadOwnedDeny(input, command);
21457
+ } catch {}
21458
+ return confineImplementer(input, shellMutationTargets(input.cwd, command, dialect, true));
21459
+ }
21460
+
21070
21461
  // packages/workit-core/src/hooks/handle.ts
21071
21462
  var usable2 = (value) => value === "native" || value === "partial";
21072
- var NONE5 = { kind: "none" };
21463
+ var NONE6 = { kind: "none" };
21073
21464
  var resendsOnChange = (descriptor) => descriptor.context.turnResend === "on-change" && usable2(descriptor.context.perTurn);
21074
21465
  var offered = new Set;
21075
21466
  var ROLES = {
@@ -21082,6 +21473,7 @@ var workitRole = (descriptor, agentType) => {
21082
21473
  const name = agentType.startsWith(prefix) ? agentType.slice(prefix.length) : "";
21083
21474
  return Object.hasOwn(ROLES, name) ? ROLES[name] : null;
21084
21475
  };
21476
+ var implementerAgent = (input, descriptor) => Boolean(input.session.agentId) && input.session.agentType !== null && workitRole(descriptor, input.session.agentType) === "implementer";
21085
21477
  var subagentSession = (session, agentId) => [session, agentId].filter((part) => Boolean(part)).join(":").replace(/[^A-Za-z0-9_.:@/+-]/g, "-").slice(0, 128);
21086
21478
  var CLAUDE_WORKTREE = /[\\/]\.claude[\\/]worktrees[\\/]/;
21087
21479
  var isolatedGit = (cwd) => `Run git as plain, separate commands inside your worktree (\`git switch -c <branch> <base>\`, ${workspaceTrailers(cwd).enabled ? '`git commit --trailer "Workit-Session=<your session>"`, `git push`; `workit doctor --fix` can install a prepare-commit-msg hook that adds the trailer' : "`git commit` (this workspace sets trailers: false: add no Workit-Session trailer), `git push`"}; add Co-Authored-By with \`git commit --trailer\` in the same commit, never by amending), never \`workit git …\`: Claude Code's worktree isolation refuses a command that wraps git and cannot be turned off (https://code.claude.com/docs/en/worktrees#how-claude-code-enforces-isolation). Never dodge that check by renaming or re-wrapping git. Use workit for the non-git verbs (\`workit check\`, \`workit ledger\`, \`workit pr\`).`;
@@ -21089,7 +21481,7 @@ var subagentStartText = (input, descriptor, event) => {
21089
21481
  const session = input.session.id ?? null;
21090
21482
  const role = workitRole(descriptor, event.agentType);
21091
21483
  const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
21092
- const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
21484
+ const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" ? hostIsolated(input.cwd, event.agentId) : CLAUDE_WORKTREE.test(input.cwd));
21093
21485
  const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
21094
21486
  if (role === "implementer" && isolated)
21095
21487
  return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${isolatedGit(input.cwd)} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
@@ -21097,7 +21489,7 @@ var subagentStartText = (input, descriptor, event) => {
21097
21489
  return `${isolatedGit(input.cwd)}
21098
21490
  ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
21099
21491
  if (role === "implementer")
21100
- return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
21492
+ return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree> git commit -m "<message>" -- <paths>\`, \`workit --cwd <worktree> check <name>\`. The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit --cwd <worktree> git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
21101
21493
  const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
21102
21494
  if (role !== "judge" || !event.agentId)
21103
21495
  return readOnly;
@@ -21128,7 +21520,7 @@ function decide(input, deps) {
21128
21520
  switch (event.kind) {
21129
21521
  case "session.start": {
21130
21522
  if (!usable2(descriptor.context.sessionStart))
21131
- return NONE5;
21523
+ return NONE6;
21132
21524
  const key = input.session.id ? `${input.host}\x00${input.session.id}` : null;
21133
21525
  const offer = event.source === "startup" && (key === null || !offered.has(key));
21134
21526
  if (offer && key !== null)
@@ -21143,26 +21535,31 @@ function decide(input, deps) {
21143
21535
  }
21144
21536
  case "context.turn": {
21145
21537
  if (!usable2(descriptor.context.perTurn))
21146
- return NONE5;
21538
+ return NONE6;
21147
21539
  const current = turnContextText(input, descriptor);
21148
21540
  const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
21149
- return withContextLine(text ? { kind: "context", text } : NONE5, promptNudge(input, event.prompt));
21541
+ return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
21150
21542
  }
21151
21543
  case "shell.pre": {
21152
21544
  const skillRead = skillReadIn(event.command, event.dialect);
21153
21545
  if (skillRead)
21154
21546
  recordSkillLoad(input, skillRead, "read");
21155
21547
  const canDeny = usable2(descriptor.shellPolicy.deny);
21156
- const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
21548
+ const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE6;
21157
21549
  if (policy.kind !== "none")
21158
21550
  return policy;
21159
21551
  const postTool = usable2(descriptor.events["shell.post"].support);
21160
21552
  if (!postTool)
21161
21553
  settlePendingCommit(input);
21162
- const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
21554
+ const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE6;
21163
21555
  if (raw.kind === "deny")
21164
21556
  return raw;
21165
- const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
21557
+ if (canDeny && implementerAgent(input, descriptor)) {
21558
+ const confined = confineImplementerShell(input, event.command, event.dialect);
21559
+ if (confined.kind === "deny")
21560
+ return confined;
21561
+ }
21562
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE6;
21166
21563
  if (gate.kind === "deny")
21167
21564
  return gate;
21168
21565
  const plan = heavyGate(input.cwd, event.command, {
@@ -21185,10 +21582,10 @@ function decide(input, deps) {
21185
21582
  command: rewrittenTo,
21186
21583
  input: event.toolInput ? { ...event.toolInput, command: rewrittenTo } : null,
21187
21584
  text
21188
- } : NONE5;
21585
+ } : NONE6;
21189
21586
  if (descriptor.transport !== "hook-process")
21190
21587
  return rewritten(null);
21191
- const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
21588
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE6, aside), shipNudge(input, event.command));
21192
21589
  if (!lease)
21193
21590
  return cwdCommand && nudged.kind !== "deny" ? rewritten(nudged.kind === "context" ? nudged.text : null) : nudged;
21194
21591
  return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
@@ -21197,9 +21594,17 @@ function decide(input, deps) {
21197
21594
  case "shell.post":
21198
21595
  if (usable2(descriptor.events["shell.post"].support))
21199
21596
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
21200
- return NONE5;
21201
- case "write.pre":
21202
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
21597
+ return NONE6;
21598
+ case "write.pre": {
21599
+ if (!usable2(descriptor.events["write.pre"].support))
21600
+ return NONE6;
21601
+ if (implementerAgent(input, descriptor)) {
21602
+ const confined = confineImplementer(input, liveTargets(input.cwd, event.paths));
21603
+ if (confined.kind === "deny")
21604
+ return confined;
21605
+ }
21606
+ return writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport));
21607
+ }
21203
21608
  case "subagent.start":
21204
21609
  if (workitRole(descriptor, event.agentType) === "judge")
21205
21610
  trackJudge(input, event.agentId, true);
@@ -21216,22 +21621,22 @@ function decide(input, deps) {
21216
21621
  case "tool.pre":
21217
21622
  if (event.skill)
21218
21623
  recordSkillLoad(input, event.skill, "tool");
21219
- return NONE5;
21624
+ return NONE6;
21220
21625
  case "stop":
21221
- return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE5;
21626
+ return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
21222
21627
  case "subagent.stop":
21223
21628
  trackJudge(input, event.agentId, false);
21224
- return NONE5;
21629
+ return NONE6;
21225
21630
  case "prompt.submit":
21226
- return NONE5;
21631
+ return NONE6;
21227
21632
  }
21228
21633
  }
21229
21634
  var failureDecision = (descriptor, event, error) => {
21230
21635
  if (event === "shell.pre" || event === "tool.pre" || event === "write.pre")
21231
- return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE5;
21636
+ return descriptor.shellPolicy.failClosed ? { kind: "deny", reason: error, unblock: null } : NONE6;
21232
21637
  if (event === "session.start" || event === "subagent.start")
21233
21638
  return { kind: "context", text: `[workit diagnostic: ${error}]` };
21234
- return NONE5;
21639
+ return NONE6;
21235
21640
  };
21236
21641
  var dispatchHook = (adapter, raw, env = process.env) => {
21237
21642
  const parsed = adapter.parse(raw, env);
@@ -21257,7 +21662,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
21257
21662
  };
21258
21663
  // packages/workit-core/src/hooks/deadline.ts
21259
21664
  import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
21260
- import path30 from "node:path";
21665
+ import path31 from "node:path";
21261
21666
  var HOOK_SKIP_TEXT = "workit context skipped: host under load";
21262
21667
  var HOOK_DEADLINE_MS = 8000;
21263
21668
  var HOST_DEADLINES = {
@@ -21275,7 +21680,7 @@ var hookDeadlineMs = (env, host = "", native) => {
21275
21680
  const row = HOST_DEADLINES[host];
21276
21681
  return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
21277
21682
  };
21278
- var skipsFile = (stateDir) => path30.join(stateDir, HOOK_SKIPS_FILE);
21683
+ var skipsFile = (stateDir) => path31.join(stateDir, HOOK_SKIPS_FILE);
21279
21684
  var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
21280
21685
  try {
21281
21686
  mkdirSync3(stateDir, { recursive: true });
@@ -21293,7 +21698,7 @@ var QUIET = new Set([
21293
21698
  ]);
21294
21699
  var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
21295
21700
  var TIMED_OUT = Symbol("timed out");
21296
- var within = async (work, ms) => {
21701
+ var within2 = async (work, ms) => {
21297
21702
  let timer;
21298
21703
  const expired = new Promise((resolve) => {
21299
21704
  timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
@@ -21324,7 +21729,7 @@ var readWithin = async (stdin, budget) => {
21324
21729
  text += String(chunk);
21325
21730
  return text;
21326
21731
  })();
21327
- const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
21732
+ const text = await within2(read, budget.startedAt + budget.deadlineMs - budget.now());
21328
21733
  return isTimedOut(text) ? null : text;
21329
21734
  };
21330
21735
  var note = (adapter, raw, env, budget, outcome, stage) => {
@@ -21353,7 +21758,7 @@ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook)
21353
21758
  if (budget.host !== null && typeof native === "string")
21354
21759
  budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
21355
21760
  const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
21356
- const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
21761
+ const result = remaining() > 0 ? await within2(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
21357
21762
  if (isTimedOut(result))
21358
21763
  return skippedHook(adapter, raw, env, budget, "dispatch");
21359
21764
  if (remaining() < 0)
@@ -21919,8 +22324,8 @@ var codexAdapter = {
21919
22324
  };
21920
22325
 
21921
22326
  // packages/workit-core/src/hooks/hosts/cursor.ts
21922
- import fs23 from "node:fs";
21923
- import path31 from "node:path";
22327
+ import fs24 from "node:fs";
22328
+ import path32 from "node:path";
21924
22329
  var undocumented = { support: "undocumented", native: null };
21925
22330
  var none = { support: "none", native: null };
21926
22331
  var CURSOR_DESCRIPTOR = {
@@ -22117,17 +22522,17 @@ var protocolEvent2 = (input) => {
22117
22522
  };
22118
22523
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
22119
22524
  var lastAssistantText = (file) => {
22120
- if (!file || !path31.isAbsolute(file))
22525
+ if (!file || !path32.isAbsolute(file))
22121
22526
  return null;
22122
22527
  try {
22123
- const size = fs23.statSync(file).size;
22528
+ const size = fs24.statSync(file).size;
22124
22529
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
22125
22530
  const buffer = Buffer.alloc(size - start);
22126
- const fd = fs23.openSync(file, "r");
22531
+ const fd = fs24.openSync(file, "r");
22127
22532
  try {
22128
- fs23.readSync(fd, buffer, 0, buffer.length, start);
22533
+ fs24.readSync(fd, buffer, 0, buffer.length, start);
22129
22534
  } finally {
22130
- fs23.closeSync(fd);
22535
+ fs24.closeSync(fd);
22131
22536
  }
22132
22537
  const lines = buffer.toString("utf8").split(`
22133
22538
  `);
@@ -22154,7 +22559,7 @@ var lastAssistantText = (file) => {
22154
22559
  return null;
22155
22560
  }
22156
22561
  };
22157
- var within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path31.sep) ? root : `${root}${path31.sep}`);
22562
+ var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path32.sep) ? root : `${root}${path32.sep}`);
22158
22563
  var hookCwd = (input) => {
22159
22564
  const cwd = existingDirectory(input.cwd);
22160
22565
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -22163,7 +22568,7 @@ var hookCwd = (input) => {
22163
22568
  };
22164
22569
  var cursorWorkspaceRoot = (input) => {
22165
22570
  const cwd = existingDirectory(input.cwd);
22166
- return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
22571
+ return cwd && input.workspace_roots.find((root) => within3(root, cwd)) || input.workspace_roots[0];
22167
22572
  };
22168
22573
  var cursorDeny = (reason) => ({
22169
22574
  permission: "deny",