@brainervirus/workit-cursor 8.15.1 → 8.17.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.
@@ -9711,6 +9711,7 @@ var init_workspaces = __esm(() => {
9711
9711
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
9712
9712
  defaultEndpoint: unknown().optional(),
9713
9713
  verification: _enum(["self", "independent"]).optional(),
9714
+ delegation: unknown().optional(),
9714
9715
  trailers: boolean2().optional(),
9715
9716
  liveSessionGuard: boolean2().optional(),
9716
9717
  fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
@@ -15032,6 +15033,16 @@ var init_workers = __esm(() => {
15032
15033
  import { spawnSync as spawnSync9 } from "node:child_process";
15033
15034
  import fs12 from "node:fs";
15034
15035
  import path16 from "node:path";
15036
+ function repoHeavyCommands(root) {
15037
+ try {
15038
+ const value = JSON.parse(fs12.readFileSync(checksConfigFile(root), "utf8"));
15039
+ const resources = isRecord3(value) ? value.resources : undefined;
15040
+ const list = isRecord3(resources) ? resources.heavyCommands : undefined;
15041
+ return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
15042
+ } catch {
15043
+ return [];
15044
+ }
15045
+ }
15035
15046
  function splitCommand(command) {
15036
15047
  const argv = [];
15037
15048
  let current = "";
@@ -15112,7 +15123,7 @@ function detectedChecks(root) {
15112
15123
  }
15113
15124
  function loadCheckConfig(cwd) {
15114
15125
  const root = checkRoot(cwd);
15115
- const file = path16.join(root, CHECKS_FILE);
15126
+ const file = checksConfigFile(root);
15116
15127
  const none = (error = null) => ({
15117
15128
  source: error ? CHECKS_FILE : "none",
15118
15129
  root,
@@ -15196,7 +15207,7 @@ function workitAvailable(env = process.env) {
15196
15207
  function checkCommand(args, version, onPath = workitAvailable()) {
15197
15208
  return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
15198
15209
  }
15199
- var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
15210
+ var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", checksConfigFile = (root) => path16.join(root, CHECKS_FILE), isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git4 = (cwd, args) => {
15200
15211
  const run = spawnSync9("git", args, {
15201
15212
  cwd,
15202
15213
  encoding: "utf8",
@@ -15648,6 +15659,42 @@ function evaluateClosure(requestedOutcome, view) {
15648
15659
  requirementIds: evaluations.map((item) => item.requirementId)
15649
15660
  });
15650
15661
  }
15662
+ function boundTaskSummary(summary) {
15663
+ const rules = new Map((summary.policy?.requirements ?? []).map((item) => [item.id, item]));
15664
+ const unmet = summary.requirements.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
15665
+ const { progress, closure } = summary;
15666
+ const next = progress.nextAction;
15667
+ const open = unmet.slice(0, BRIEF_OPEN).map((item) => ({
15668
+ dimension: rules.get(item.requirementId)?.dimension ?? "verification",
15669
+ before: rules.get(item.requirementId)?.before ?? "close",
15670
+ status: item.status
15671
+ }));
15672
+ const brief = {
15673
+ id: summary.id,
15674
+ revision: summary.revision,
15675
+ workspaceRevision: summary.workspaceRevision,
15676
+ updatedAt: summary.updatedAt,
15677
+ objective: clip(summary.objective),
15678
+ status: summary.status,
15679
+ closure: closure && {
15680
+ outcome: closure.outcome,
15681
+ at: closure.at,
15682
+ summary: clip(closure.summary)
15683
+ },
15684
+ progress: {
15685
+ summary: clip(progress.summary),
15686
+ nextAction: next && clip(next),
15687
+ blockers: progress.blockers.slice(0, BRIEF_BLOCKERS).map((item) => ({
15688
+ reason: clip(item.reason),
15689
+ dependentAction: clip(item.dependentAction)
15690
+ }))
15691
+ },
15692
+ requirements: { total: summary.requirements.length, unmet: unmet.length, open },
15693
+ truncated: false
15694
+ };
15695
+ brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
15696
+ return brief;
15697
+ }
15651
15698
  var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`), relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".", canonicalPath = (value) => {
15652
15699
  if (typeof value !== "string" || !value)
15653
15700
  return null;
@@ -15867,7 +15914,15 @@ var digestBytes = (value) => createHash10("sha256").update(value).digest("hex"),
15867
15914
  if (dimension === "decisions")
15868
15915
  return "decision";
15869
15916
  return "worker report";
15870
- }, observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
15917
+ }, observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close", BRIEF_TEXT = 250, BRIEF_BLOCKERS = 3, BRIEF_OPEN = 8, clip = (value) => {
15918
+ const fits = (text) => Buffer.byteLength(JSON.stringify(text)) <= BRIEF_TEXT;
15919
+ if (fits(value))
15920
+ return value;
15921
+ let length = Math.min(value.length, BRIEF_TEXT);
15922
+ while (length > 0 && !fits(`${value.slice(0, length)}…`))
15923
+ length -= 1;
15924
+ return `${value.slice(0, length)}…`;
15925
+ };
15871
15926
  var init_task_evaluation = __esm(() => {
15872
15927
  init_task_contract();
15873
15928
  init_check_config();
@@ -16294,6 +16349,15 @@ class WorkitCore {
16294
16349
  if (!parsed.ok)
16295
16350
  return parsed;
16296
16351
  const input = parsed.data;
16352
+ if (this.context.readOnlyChild) {
16353
+ if (input.action !== "inspect")
16354
+ return failure2("permission_denied", "child sessions can only inspect the task summary");
16355
+ const task = this.store.readTask(input.taskId);
16356
+ if (!task.ok)
16357
+ return task;
16358
+ const summary = this.summary(task.data);
16359
+ return summary.ok ? { ...summary, data: boundTaskSummary(summary.data) } : summary;
16360
+ }
16297
16361
  if ((this.context.workerId ?? null) !== null) {
16298
16362
  if (input.action !== "inspect" && input.action !== "list")
16299
16363
  return failure2("permission_denied", "helpers cannot control task lifecycle or scope");
@@ -18696,7 +18760,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
18696
18760
  RECENT.delete(RECENT.keys().next().value);
18697
18761
  return found;
18698
18762
  }
18699
- function walk2(command, dialect, depth) {
18763
+ function commandLines(command, dialect = "posix") {
18764
+ const lines = [];
18765
+ try {
18766
+ walk2(command, dialect, 0, lines);
18767
+ } catch (error) {
18768
+ throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
18769
+ }
18770
+ return lines;
18771
+ }
18772
+ function walk2(command, dialect, depth, seen) {
18700
18773
  const found = [];
18701
18774
  let dir = null;
18702
18775
  const groups = [];
@@ -18707,7 +18780,8 @@ function walk2(command, dialect, depth) {
18707
18780
  const nested = (script, inner) => {
18708
18781
  if (!script || depth >= MAX_DEPTH)
18709
18782
  return;
18710
- for (const invocation of rawInvocations(script, inner, depth + 1))
18783
+ const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
18784
+ for (const invocation of inside)
18711
18785
  found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
18712
18786
  };
18713
18787
  const visit = (input, stdin, hops) => {
@@ -18719,11 +18793,17 @@ function walk2(command, dialect, depth) {
18719
18793
  const name = commandName(words[0]);
18720
18794
  if (!name)
18721
18795
  return;
18796
+ seen?.push(words);
18797
+ if (name === "-c" || name === "--command")
18798
+ return nested(words[1], "posix");
18722
18799
  const runs = (lines) => {
18723
18800
  if (hops < MAX_DEPTH)
18724
18801
  for (const next of lines)
18725
18802
  visit(next, undefined, hops + 1);
18726
18803
  };
18804
+ const wrapped = seen ? null : workitCheckTail(words);
18805
+ if (wrapped)
18806
+ return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
18727
18807
  if (CD.has(name)) {
18728
18808
  const target = cdTarget(words);
18729
18809
  dir = target === null ? os5.homedir() : joinDir(dir, target);
@@ -18800,6 +18880,49 @@ function walk2(command, dialect, depth) {
18800
18880
  }
18801
18881
  return found;
18802
18882
  }
18883
+ function workitCheckTail(words) {
18884
+ let at = 0;
18885
+ if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
18886
+ at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
18887
+ if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
18888
+ return null;
18889
+ } else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
18890
+ return null;
18891
+ const args = words.slice(at + 1);
18892
+ if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
18893
+ return null;
18894
+ const split = args.indexOf("--");
18895
+ const tail = split < 0 ? [] : args.slice(split + 1);
18896
+ if (!tail.length)
18897
+ return null;
18898
+ return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
18899
+ }
18900
+ function simpleCommand(command, dialect = "posix", depth = 0) {
18901
+ let segments;
18902
+ try {
18903
+ segments = segmentsOf(command, dialect);
18904
+ } catch {
18905
+ return null;
18906
+ }
18907
+ if (segments.length !== 1)
18908
+ return null;
18909
+ const [segment] = segments;
18910
+ if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
18911
+ return null;
18912
+ const { words } = stripPrefixes(segment.words);
18913
+ const name = commandName(words[0]);
18914
+ if (!name || depth >= MAX_DEPTH)
18915
+ return null;
18916
+ if (SHELLS.has(name)) {
18917
+ const script = shellScript(words, undefined);
18918
+ return script ? simpleCommand(script, "posix", depth + 1) : null;
18919
+ }
18920
+ if (name === "-c" || name === "--command")
18921
+ return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
18922
+ if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
18923
+ return null;
18924
+ return words;
18925
+ }
18803
18926
  function workitInvocations(command, dialect = "posix", depth = 0) {
18804
18927
  const found = [];
18805
18928
  for (const { words } of segmentsOf(command, dialect)) {
@@ -19216,7 +19339,7 @@ var NONE2, SHELLS, POWERSHELLS, EVALS, WRAPPERS, WRAPPER_VALUES, KEYWORDS, DECLA
19216
19339
  ...Object.keys(env).length ? { env } : {},
19217
19340
  ...stdin === undefined ? {} : { stdin }
19218
19341
  };
19219
- }, RECENT, LAUNCHERS, WORKIT_BIN, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
19342
+ }, RECENT, LAUNCHERS, WORKIT_BIN, WORKIT_PACKAGE, PACKAGE_RUNNERS, PUSH_VALUE_OPTIONS, CURRENT_BRANCH, pushDestination = (destination) => {
19220
19343
  if (destination === "HEAD" || destination === "@" || CURRENT_BRANCH.test(destination))
19221
19344
  return { name: null };
19222
19345
  if (destination.startsWith("refs/heads/"))
@@ -19908,6 +20031,8 @@ var init_raw_git = __esm(() => {
19908
20031
  "command"
19909
20032
  ]);
19910
20033
  WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
20034
+ WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
20035
+ PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
19911
20036
  PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
19912
20037
  CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
19913
20038
  VALUE_OPTIONS = {
@@ -20147,12 +20272,808 @@ var init_policy = __esm(() => {
20147
20272
  init_shell_words();
20148
20273
  });
20149
20274
 
20150
- // packages/workit-core/src/core/live-session.ts
20151
- import { spawnSync as spawnSync13 } from "node:child_process";
20152
- import { createHash as createHash15 } from "node:crypto";
20275
+ // packages/workit-core/src/core/heavy-rules.ts
20153
20276
  import fs17 from "node:fs";
20154
20277
  import os6 from "node:os";
20155
20278
  import path22 from "node:path";
20279
+ function parseJsonc(text) {
20280
+ let out = "";
20281
+ for (let index = 0;index < text.length; index += 1) {
20282
+ const char = text[index];
20283
+ if (char === '"') {
20284
+ const end = /"(?:[^"\\]|\\.)*"/y;
20285
+ end.lastIndex = index;
20286
+ const match = end.exec(text);
20287
+ if (!match)
20288
+ throw new Error("unterminated string");
20289
+ out += match[0];
20290
+ index += match[0].length - 1;
20291
+ } else if (char === "/" && text[index + 1] === "/") {
20292
+ while (index < text.length && text[index] !== `
20293
+ `)
20294
+ index += 1;
20295
+ out += `
20296
+ `;
20297
+ } else if (char === "/" && text[index + 1] === "*") {
20298
+ const close = text.indexOf("*/", index + 2);
20299
+ if (close < 0)
20300
+ throw new Error("unterminated comment");
20301
+ index = close + 1;
20302
+ } else
20303
+ out += char;
20304
+ }
20305
+ return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
20306
+ }
20307
+ function gitTop(cwd) {
20308
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
20309
+ if (fs17.existsSync(path22.join(dir, ".git")))
20310
+ return dir;
20311
+ if (path22.dirname(dir) === dir)
20312
+ return null;
20313
+ }
20314
+ }
20315
+ function claudeSettingsFiles(cwd, env, platform = process.platform) {
20316
+ const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
20317
+ const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
20318
+ return [
20319
+ ...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
20320
+ path22.join(user, "settings.json"),
20321
+ path22.join(project, ".claude", "settings.json"),
20322
+ path22.join(project, ".claude", "settings.local.json")
20323
+ ];
20324
+ }
20325
+ function claudeRules(cwd, env = process.env) {
20326
+ const rules = EMPTY();
20327
+ try {
20328
+ for (const file of claudeSettingsFiles(cwd, env)) {
20329
+ const text = readText2(file);
20330
+ if (text === null)
20331
+ continue;
20332
+ const value = JSON.parse(text);
20333
+ if (!isRecord4(value))
20334
+ return "unreadable";
20335
+ const permissions = value.permissions;
20336
+ if (permissions === undefined)
20337
+ continue;
20338
+ if (!isRecord4(permissions))
20339
+ return "unreadable";
20340
+ for (const [key, bucket] of [
20341
+ ["deny", rules.deny],
20342
+ ["ask", rules.deny],
20343
+ ["allow", rules.allow]
20344
+ ]) {
20345
+ const list = permissions[key];
20346
+ if (list === undefined)
20347
+ continue;
20348
+ if (!Array.isArray(list))
20349
+ return "unreadable";
20350
+ for (const entry of list) {
20351
+ if (typeof entry !== "string")
20352
+ return "unreadable";
20353
+ const pattern = bashPattern(entry);
20354
+ if (pattern !== null)
20355
+ bucket.push(pattern);
20356
+ }
20357
+ }
20358
+ }
20359
+ } catch {
20360
+ return "unreadable";
20361
+ }
20362
+ return rules;
20363
+ }
20364
+ function opencodeRules(cwd, env = process.env) {
20365
+ const rules = EMPTY();
20366
+ try {
20367
+ const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
20368
+ const files = [
20369
+ ...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
20370
+ ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
20371
+ ...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
20372
+ ];
20373
+ for (const file of files) {
20374
+ const text = readText2(file);
20375
+ if (text !== null && !opencodeConfig(parseJsonc(text), rules))
20376
+ return "unreadable";
20377
+ }
20378
+ if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
20379
+ return "unreadable";
20380
+ } catch {
20381
+ return "unreadable";
20382
+ }
20383
+ return rules;
20384
+ }
20385
+ function codexRules(cwd, env = process.env) {
20386
+ const rules = EMPTY();
20387
+ try {
20388
+ const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
20389
+ const top = gitTop(cwd) ?? cwd;
20390
+ for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
20391
+ let files;
20392
+ try {
20393
+ files = fs17.readdirSync(dir).filter((name) => name.endsWith(".rules"));
20394
+ } catch (error) {
20395
+ if (error.code === "ENOENT")
20396
+ continue;
20397
+ return "unreadable";
20398
+ }
20399
+ for (const name of files) {
20400
+ const text = readText2(path22.join(dir, name)) ?? "";
20401
+ for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
20402
+ const rule = codexRule(part);
20403
+ if (!rule)
20404
+ return "unreadable";
20405
+ (rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
20406
+ }
20407
+ }
20408
+ }
20409
+ } catch {
20410
+ return "unreadable";
20411
+ }
20412
+ return rules;
20413
+ }
20414
+ function cursorRules(cwd, env = process.env) {
20415
+ const rules = EMPTY();
20416
+ try {
20417
+ const top = gitTop(cwd) ?? cwd;
20418
+ for (const file of [
20419
+ path22.join(homeOf(env), ".cursor", "cli-config.json"),
20420
+ path22.join(top, ".cursor", "cli.json")
20421
+ ]) {
20422
+ const text = readText2(file);
20423
+ if (text === null)
20424
+ continue;
20425
+ const value = JSON.parse(text);
20426
+ if (!isRecord4(value))
20427
+ return "unreadable";
20428
+ if (value.permissions === undefined)
20429
+ continue;
20430
+ if (!isRecord4(value.permissions))
20431
+ return "unreadable";
20432
+ for (const [key, bucket] of [
20433
+ ["deny", rules.deny],
20434
+ ["allow", rules.allow]
20435
+ ]) {
20436
+ const list = value.permissions[key];
20437
+ if (list === undefined)
20438
+ continue;
20439
+ if (!Array.isArray(list))
20440
+ return "unreadable";
20441
+ for (const entry of list) {
20442
+ if (typeof entry !== "string")
20443
+ return "unreadable";
20444
+ const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
20445
+ if (match)
20446
+ bucket.push(match[1] ?? "");
20447
+ }
20448
+ }
20449
+ }
20450
+ } catch {
20451
+ return "unreadable";
20452
+ }
20453
+ return rules;
20454
+ }
20455
+ function names(pattern, words) {
20456
+ const token = firstToken(pattern);
20457
+ if (!token || token === "*" || token.includes("*"))
20458
+ return true;
20459
+ const wanted = baseName(token);
20460
+ return words.some((word) => baseName(word) === wanted);
20461
+ }
20462
+ function wrapAllowed(read, words, command, options = {}) {
20463
+ if (read === "unreadable")
20464
+ return { ok: false, why: "named" };
20465
+ if (read.deny.some((pattern) => names(pattern, words)))
20466
+ return { ok: false, why: "named" };
20467
+ if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
20468
+ return { ok: false, why: "allowlisted" };
20469
+ return { ok: true };
20470
+ }
20471
+ var EMPTY = () => ({ deny: [], allow: [] }), readText2 = (file) => {
20472
+ try {
20473
+ return fs17.readFileSync(file, "utf8");
20474
+ } catch (error) {
20475
+ if (error.code === "ENOENT")
20476
+ return null;
20477
+ throw error;
20478
+ }
20479
+ }, isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectDirs = (cwd) => {
20480
+ const top = gitTop(cwd);
20481
+ const dirs = [];
20482
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
20483
+ dirs.push(dir);
20484
+ if (!top || dir === top || path22.dirname(dir) === dir)
20485
+ return dirs;
20486
+ }
20487
+ }, homeOf = (env) => env.HOME || os6.homedir(), CLAUDE_MANAGED, bashPattern = (entry) => {
20488
+ const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
20489
+ return match ? match[1] ?? "" : null;
20490
+ }, OPENCODE_NAMES, opencodePermission = (permission, rules) => {
20491
+ if (permission === undefined)
20492
+ return true;
20493
+ if (typeof permission === "string") {
20494
+ (permission === "allow" ? rules.allow : rules.deny).push("");
20495
+ return ["allow", "ask", "deny"].includes(permission);
20496
+ }
20497
+ if (!isRecord4(permission))
20498
+ return false;
20499
+ for (const key of ["bash", "shell"]) {
20500
+ const value = permission[key];
20501
+ if (value === undefined)
20502
+ continue;
20503
+ if (typeof value === "string") {
20504
+ if (!["allow", "ask", "deny"].includes(value))
20505
+ return false;
20506
+ (value === "allow" ? rules.allow : rules.deny).push("");
20507
+ continue;
20508
+ }
20509
+ if (!isRecord4(value))
20510
+ return false;
20511
+ for (const [pattern, action] of Object.entries(value)) {
20512
+ if (action !== "allow" && action !== "ask" && action !== "deny")
20513
+ return false;
20514
+ (action === "allow" ? rules.allow : rules.deny).push(pattern);
20515
+ }
20516
+ }
20517
+ return true;
20518
+ }, opencodeConfig = (value, rules) => {
20519
+ if (!isRecord4(value))
20520
+ return false;
20521
+ if (!opencodePermission(value.permission, rules))
20522
+ return false;
20523
+ if (value.agent === undefined)
20524
+ return true;
20525
+ if (!isRecord4(value.agent))
20526
+ return false;
20527
+ return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
20528
+ }, codexRule = (body) => {
20529
+ const open = /\bpattern\s*=\s*\[/u.exec(body);
20530
+ if (!open)
20531
+ return null;
20532
+ const words = [];
20533
+ let index = open.index + open[0].length;
20534
+ for (;; ) {
20535
+ while (/[\s,]/u.test(body[index] ?? ""))
20536
+ index += 1;
20537
+ if (body[index] === "]")
20538
+ break;
20539
+ if (body[index] !== '"')
20540
+ return null;
20541
+ const literal = /"((?:[^"\\]|\\.)*)"/y;
20542
+ literal.lastIndex = index;
20543
+ const match = literal.exec(body);
20544
+ if (!match)
20545
+ return null;
20546
+ words.push(JSON.parse(`"${match[1]}"`));
20547
+ index = literal.lastIndex;
20548
+ }
20549
+ const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
20550
+ if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
20551
+ return null;
20552
+ return { pattern: words.join(" "), decision };
20553
+ }, baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, ""), firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "", literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, ""), coversWrapper = (pattern) => {
20554
+ const token = firstToken(pattern);
20555
+ if (!token || token.includes("*"))
20556
+ return true;
20557
+ const words = literalPrefix(pattern).split(/\s+/u);
20558
+ if (baseName(words[0]) !== "workit")
20559
+ return false;
20560
+ return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
20561
+ const given = words[at + 1];
20562
+ return given === word || word.startsWith(given);
20563
+ });
20564
+ }, coversCommand = (pattern, command) => {
20565
+ const prefix = literalPrefix(pattern);
20566
+ if (!prefix)
20567
+ return true;
20568
+ return !prefix.includes("*") && command.trim().startsWith(prefix);
20569
+ };
20570
+ var init_heavy_rules = __esm(() => {
20571
+ CLAUDE_MANAGED = {
20572
+ linux: "/etc/claude-code/managed-settings.json",
20573
+ darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
20574
+ win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
20575
+ };
20576
+ OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
20577
+ });
20578
+
20579
+ // packages/workit-core/src/core/heavy.ts
20580
+ import fs18 from "node:fs";
20581
+ import os7 from "node:os";
20582
+ import path23 from "node:path";
20583
+ function stateDir(env, platform = process.platform) {
20584
+ if (env.WORKFLOW_TOOLKIT_STATE)
20585
+ return env.WORKFLOW_TOOLKIT_STATE;
20586
+ if (env.XDG_STATE_HOME)
20587
+ return path23.join(env.XDG_STATE_HOME, "workit");
20588
+ const home = env.HOME || os7.homedir();
20589
+ if (platform === "darwin")
20590
+ return path23.join(home, "Library", "Application Support", "workit");
20591
+ if (platform === "win32")
20592
+ return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
20593
+ return path23.join(home, ".local", "state", "workit");
20594
+ }
20595
+ function positional2(words, from, values = PM_VALUES) {
20596
+ let index = from;
20597
+ while (index < words.length && words[index].startsWith("-")) {
20598
+ if (words[index] === "--")
20599
+ return index + 1;
20600
+ index += values.has(words[index]) ? 2 : 1;
20601
+ }
20602
+ return index;
20603
+ }
20604
+ function builtIn(words, depth = 0) {
20605
+ if (depth > 6 || !words.length)
20606
+ return null;
20607
+ const name = baseName2(words[0]);
20608
+ const label = () => words.slice(0, 3).join(" ");
20609
+ const inner = (from) => builtIn(words.slice(from), depth + 1);
20610
+ if (WORKIT.test(name))
20611
+ return null;
20612
+ if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
20613
+ return null;
20614
+ if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
20615
+ return null;
20616
+ if (name === "npx" || name === "bunx" || name === "pnpx")
20617
+ return inner(positional2(words, 1));
20618
+ if (name === "corepack")
20619
+ return inner(1);
20620
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
20621
+ const at = positional2(words, 1);
20622
+ const command = words[at];
20623
+ if (command === undefined)
20624
+ return name === "yarn" ? "yarn (install)" : null;
20625
+ if (name === "deno") {
20626
+ if (["test", "bench", "check", "compile", "lint"].includes(command))
20627
+ return label();
20628
+ if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
20629
+ return label();
20630
+ return null;
20631
+ }
20632
+ if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
20633
+ return label();
20634
+ if (name === "bun" && command === "build")
20635
+ return label();
20636
+ if (EXEC.has(command))
20637
+ return inner(positional2(words, at + 1));
20638
+ if (name === "yarn" && command === "workspace")
20639
+ return inner(at + 2);
20640
+ if (name === "yarn" && command === "workspaces")
20641
+ return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
20642
+ if (NPM_RUN.has(command)) {
20643
+ const script = words[positional2(words, at + 1)];
20644
+ if (script === undefined || WATCH_SCRIPT.test(script))
20645
+ return null;
20646
+ if (HEAVY_SCRIPT.test(script))
20647
+ return label();
20648
+ return name === "bun" ? inner(positional2(words, at + 1)) : null;
20649
+ }
20650
+ if (name === "npm")
20651
+ return null;
20652
+ if (WATCH_SCRIPT.test(command))
20653
+ return null;
20654
+ if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
20655
+ return label();
20656
+ return inner(at);
20657
+ }
20658
+ if (PYTHONS.test(name)) {
20659
+ const at = words.indexOf("-m");
20660
+ return at > 0 && words[at + 1] ? inner(at + 1) : null;
20661
+ }
20662
+ if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
20663
+ const at = positional2(words, 1);
20664
+ const command = words[at];
20665
+ if (command === "run")
20666
+ return inner(positional2(words, at + 1));
20667
+ if (command === "pip")
20668
+ return inner(at);
20669
+ return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
20670
+ }
20671
+ if (name === "bundle") {
20672
+ const command = words[positional2(words, 1)];
20673
+ if (command === "exec")
20674
+ return inner(positional2(words, 2));
20675
+ return command === "install" || command === undefined ? label() : null;
20676
+ }
20677
+ if (name === "make" || name === "gmake") {
20678
+ const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
20679
+ if (targets.some((target) => WATCH_SCRIPT.test(target)))
20680
+ return null;
20681
+ return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
20682
+ }
20683
+ if (name === "rake") {
20684
+ const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
20685
+ return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
20686
+ }
20687
+ if (name === "nx") {
20688
+ const at = positional2(words, 1);
20689
+ const command = words[at] ?? "";
20690
+ if (["run-many", "affected"].includes(command))
20691
+ return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
20692
+ if (command === "run") {
20693
+ const target = (words[at + 1] ?? "").split(":")[1] ?? "";
20694
+ return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
20695
+ }
20696
+ return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
20697
+ }
20698
+ if (name === "docker" || name === "podman" || name === "nerdctl") {
20699
+ const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
20700
+ const command = words[at];
20701
+ if (command === "build")
20702
+ return label();
20703
+ if (command === "buildx" || command === "image" || command === "builder")
20704
+ return words[positional2(words, at + 1)] === "build" ? label() : null;
20705
+ if (command === "compose") {
20706
+ const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
20707
+ const action = words[at2];
20708
+ if (action === "build")
20709
+ return label();
20710
+ return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
20711
+ }
20712
+ return null;
20713
+ }
20714
+ const rule = TOOLS[name];
20715
+ if (!rule)
20716
+ return null;
20717
+ if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
20718
+ return null;
20719
+ if (rule === "any")
20720
+ return label();
20721
+ const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
20722
+ return command !== undefined && rule.has(command) ? label() : null;
20723
+ }
20724
+ function parsePattern(pattern) {
20725
+ const words = pattern.trim().split(/\s+/u).filter(Boolean);
20726
+ return words.length ? words.map(globWord) : null;
20727
+ }
20728
+ function machinePatterns(env) {
20729
+ const value = readJson(heavyCommandsFile(env));
20730
+ return isRecord5(value) ? stringList(value.patterns) : [];
20731
+ }
20732
+ function repoPatterns(cwd) {
20733
+ return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
20734
+ }
20735
+ function heavyLines(lines, options) {
20736
+ const found = [];
20737
+ let patterns = null;
20738
+ for (const line of lines) {
20739
+ const reason = builtIn(line);
20740
+ if (reason) {
20741
+ found.push({ line, source: "catalog", reason });
20742
+ continue;
20743
+ }
20744
+ patterns ??= (options.patterns ?? [
20745
+ ...machinePatterns(options.env ?? process.env).map((pattern) => ({
20746
+ pattern,
20747
+ from: "machine"
20748
+ })),
20749
+ ...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
20750
+ ]).flatMap((entry) => {
20751
+ const matchers = parsePattern(entry.pattern);
20752
+ return matchers ? [{ ...entry, matchers }] : [];
20753
+ });
20754
+ const hit = patterns.find((entry) => matches(entry.matchers, line));
20755
+ if (hit)
20756
+ found.push({
20757
+ line,
20758
+ source: { pattern: hit.pattern, from: hit.from },
20759
+ reason: `${hit.from} pattern "${hit.pattern}"`
20760
+ });
20761
+ }
20762
+ return found;
20763
+ }
20764
+ function heavyPlan(command, options) {
20765
+ const dialect = options.dialect ?? "posix";
20766
+ if (dialect !== "posix" || !gitTop(options.cwd))
20767
+ return { kind: "none" };
20768
+ const lines = linesOf(command, dialect);
20769
+ if (!lines)
20770
+ return { kind: "none" };
20771
+ const heavy = heavyLines(lines, options);
20772
+ if (!heavy.length)
20773
+ return { kind: "none" };
20774
+ const simple = simpleCommand(command, dialect);
20775
+ const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
20776
+ const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
20777
+ words: match.line,
20778
+ text: match.line.join(" ")
20779
+ }));
20780
+ if (options.rules !== null)
20781
+ for (const { words, text } of judged) {
20782
+ const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
20783
+ if (!verdict.ok)
20784
+ return {
20785
+ kind: "passthrough",
20786
+ note: `workit: \`${command.trim()}\` is heavy but runs outside the heavy-check lease because a host permission rule names it (or \`workit\` is allowlisted); the host decides.`
20787
+ };
20788
+ }
20789
+ if (single) {
20790
+ const rewrite = leaseRewrite(command);
20791
+ return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
20792
+ }
20793
+ const parts = heavy.map((match) => leaseCommand(match.line));
20794
+ return {
20795
+ kind: "split",
20796
+ reason: `heavy_command: \`${command.trim()}\` chains heavy work (${heavy.map((match) => match.reason).join("; ")}) with other commands, and a chain cannot run under the machine-wide heavy-check lease. Run each heavy part alone as \`workit check --heavy -- <segment>\` (here: ${parts.map((part) => `\`${part}\``).join("; ")}); run the other parts as their own commands.`,
20797
+ unblock: parts[0] ?? null
20798
+ };
20799
+ }
20800
+ function workitOnPath2(env = process.env) {
20801
+ const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
20802
+ for (const dir of (env.PATH ?? "").split(path23.delimiter))
20803
+ for (const name of names) {
20804
+ if (!dir)
20805
+ continue;
20806
+ try {
20807
+ fs18.accessSync(path23.join(dir, name), fs18.constants.X_OK);
20808
+ return true;
20809
+ } catch {}
20810
+ }
20811
+ return false;
20812
+ }
20813
+ function leaseRewrite(command) {
20814
+ const text = command.trim();
20815
+ const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
20816
+ const rest = text.slice(prefix.length).trim();
20817
+ if (!rest)
20818
+ return null;
20819
+ return `${prefix}workit check --heavy -- ${rest}`;
20820
+ }
20821
+ var HEAVY_COMMANDS_FILE = "heavy-commands.json", heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE), HEAVY_SCRIPT, INFO_FLAGS, WATCH_FLAG, WATCH_SCRIPT, sub = (...names) => new Set(names), TOOLS, PM_VALUES, baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, ""), WORKIT, PYTHONS, NPM_INSTALL, NPM_RUN, NPM_TEST, EXEC, globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u"), matches = (matchers, words) => matchers.length <= words.length && matchers.every((matcher, index) => index === 0 ? matcher.test(words[0]) || matcher.test(baseName2(words[0])) : matcher.test(words[index])), readJson = (file) => {
20822
+ try {
20823
+ return JSON.parse(fs18.readFileSync(file, "utf8"));
20824
+ } catch {
20825
+ return;
20826
+ }
20827
+ }, isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [], linesOf = (command, dialect) => {
20828
+ try {
20829
+ return commandLines(command, dialect);
20830
+ } catch {
20831
+ return null;
20832
+ }
20833
+ }, quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`, leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`, ASSIGNMENTS;
20834
+ var init_heavy = __esm(() => {
20835
+ init_raw_git();
20836
+ init_check_config();
20837
+ init_heavy_rules();
20838
+ HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
20839
+ INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
20840
+ WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
20841
+ WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
20842
+ TOOLS = {
20843
+ jest: "any",
20844
+ vitest: "any",
20845
+ mocha: "any",
20846
+ ava: "any",
20847
+ tap: "any",
20848
+ karma: "any",
20849
+ jasmine: "any",
20850
+ nyc: "any",
20851
+ c8: "any",
20852
+ tsc: "any",
20853
+ "vue-tsc": "any",
20854
+ "svelte-check": "any",
20855
+ eslint: "any",
20856
+ stylelint: "any",
20857
+ tslint: "any",
20858
+ webpack: "any",
20859
+ "webpack-cli": "any",
20860
+ rollup: "any",
20861
+ parcel: "any",
20862
+ tsup: "any",
20863
+ vite: sub("build", "optimize"),
20864
+ next: sub("build", "lint"),
20865
+ nuxt: sub("build", "generate"),
20866
+ nuxi: sub("build", "generate"),
20867
+ astro: sub("build", "check"),
20868
+ ng: sub("build", "b", "test", "t", "e2e", "lint"),
20869
+ "react-scripts": sub("build", "test"),
20870
+ playwright: sub("test", "install"),
20871
+ cypress: sub("run"),
20872
+ wdio: "any",
20873
+ testcafe: "any",
20874
+ turbo: "any",
20875
+ lerna: sub("run", "exec", "bootstrap"),
20876
+ "sonar-scanner": "any",
20877
+ sonarless: sub("scan"),
20878
+ "sonarless-mcp": sub("scan"),
20879
+ go: sub("test", "build", "vet", "install", "generate"),
20880
+ "golangci-lint": sub("run"),
20881
+ gotestsum: "any",
20882
+ staticcheck: "any",
20883
+ cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
20884
+ mvn: "any",
20885
+ mvnw: "any",
20886
+ gradle: "any",
20887
+ gradlew: "any",
20888
+ ant: "any",
20889
+ sbt: "any",
20890
+ bazel: sub("build", "test", "run", "coverage"),
20891
+ bazelisk: sub("build", "test", "run", "coverage"),
20892
+ buck2: sub("build", "test", "run"),
20893
+ dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
20894
+ msbuild: "any",
20895
+ pytest: "any",
20896
+ "py.test": "any",
20897
+ tox: "any",
20898
+ nox: "any",
20899
+ mypy: "any",
20900
+ pyright: "any",
20901
+ pylint: "any",
20902
+ coverage: sub("run"),
20903
+ maturin: sub("build", "develop"),
20904
+ pip: sub("install", "wheel"),
20905
+ pip3: sub("install", "wheel"),
20906
+ rspec: "any",
20907
+ rails: sub("test", "assets:precompile"),
20908
+ phpunit: "any",
20909
+ pest: "any",
20910
+ phpstan: "any",
20911
+ psalm: "any",
20912
+ composer: sub("install", "update", "require", "test"),
20913
+ mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
20914
+ swift: sub("build", "test"),
20915
+ xcodebuild: "any",
20916
+ flutter: sub("build", "test"),
20917
+ dart: sub("test", "compile"),
20918
+ pod: sub("install"),
20919
+ cmake: sub("--build"),
20920
+ ninja: "any",
20921
+ meson: sub("compile", "test"),
20922
+ ctest: "any",
20923
+ scons: "any",
20924
+ "docker-compose": sub("build"),
20925
+ buildah: sub("bud", "build"),
20926
+ earthly: "any"
20927
+ };
20928
+ PM_VALUES = new Set([
20929
+ "--prefix",
20930
+ "-w",
20931
+ "--workspace",
20932
+ "-C",
20933
+ "--dir",
20934
+ "--filter",
20935
+ "-F",
20936
+ "--cwd",
20937
+ "-p",
20938
+ "--package",
20939
+ "--cache",
20940
+ "--registry"
20941
+ ]);
20942
+ WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
20943
+ PYTHONS = /^(?:python[\d.]*|py)$/u;
20944
+ NPM_INSTALL = new Set([
20945
+ "ci",
20946
+ "clean-install",
20947
+ "cit",
20948
+ "install-test",
20949
+ "it",
20950
+ "install",
20951
+ "i",
20952
+ "in",
20953
+ "add",
20954
+ "rebuild",
20955
+ "update",
20956
+ "up"
20957
+ ]);
20958
+ NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
20959
+ NPM_TEST = new Set(["test", "t", "tst"]);
20960
+ EXEC = new Set(["exec", "x", "dlx"]);
20961
+ ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
20962
+ });
20963
+
20964
+ // packages/workit-core/src/core/heavy-words.ts
20965
+ import fs19 from "node:fs";
20966
+ import os8 from "node:os";
20967
+ import path24 from "node:path";
20968
+ function maybeHeavy(command, cwd, env = process.env) {
20969
+ try {
20970
+ const words = wordsOf(command);
20971
+ if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
20972
+ return true;
20973
+ const extra = patternWords(cwd, env);
20974
+ return extra === null || words.some((word) => extra.has(word));
20975
+ } catch {
20976
+ return true;
20977
+ }
20978
+ }
20979
+ var HEAVY_WORDS, wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean), stateDir2 = (env) => {
20980
+ if (env.WORKFLOW_TOOLKIT_STATE)
20981
+ return env.WORKFLOW_TOOLKIT_STATE;
20982
+ if (env.XDG_STATE_HOME)
20983
+ return path24.join(env.XDG_STATE_HOME, "workit");
20984
+ const home = env.HOME || os8.homedir();
20985
+ if (process.platform === "darwin")
20986
+ return path24.join(home, "Library", "Application Support", "workit");
20987
+ if (process.platform === "win32")
20988
+ return path24.join(env.LOCALAPPDATA ?? path24.join(home, "AppData", "Local"), "workit");
20989
+ return path24.join(home, ".local", "state", "workit");
20990
+ }, readJson2 = (file) => {
20991
+ try {
20992
+ return JSON.parse(fs19.readFileSync(file, "utf8"));
20993
+ } catch {
20994
+ return;
20995
+ }
20996
+ }, patternWords = (cwd, env) => {
20997
+ const patterns = [];
20998
+ const machine = readJson2(path24.join(stateDir2(env), "heavy-commands.json"));
20999
+ if (Array.isArray(machine?.patterns))
21000
+ patterns.push(...machine.patterns);
21001
+ for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
21002
+ if (fs19.existsSync(path24.join(dir, ".git"))) {
21003
+ patterns.push(...repoHeavyCommands(dir));
21004
+ break;
21005
+ }
21006
+ if (path24.dirname(dir) === dir)
21007
+ break;
21008
+ }
21009
+ const words = new Set;
21010
+ for (const pattern of patterns) {
21011
+ if (typeof pattern !== "string")
21012
+ continue;
21013
+ const first = wordsOf(pattern)[0];
21014
+ if (!first || first.includes("*"))
21015
+ return null;
21016
+ words.add(first);
21017
+ }
21018
+ return words;
21019
+ };
21020
+ var init_heavy_words = __esm(() => {
21021
+ init_check_config();
21022
+ HEAVY_WORDS = new Set(("npm npx pnpm pnpx yarn bun bunx deno corepack make gmake docker podman nerdctl " + "python python3 py uv poetry pdm pipenv hatch rye bundle rake nx " + "jest vitest mocha ava tap karma jasmine nyc c8 tsc vue-tsc svelte-check eslint stylelint " + "tslint webpack webpack-cli rollup parcel tsup vite next nuxt nuxi astro ng react-scripts " + "playwright cypress wdio testcafe turbo lerna sonar-scanner sonarless sonarless-mcp go " + "golangci-lint gotestsum staticcheck cargo mvn mvnw gradle gradlew ant sbt bazel bazelisk " + "buck2 dotnet msbuild pytest py.test tox nox mypy pyright pylint coverage maturin pip pip3 " + "rspec rails phpunit pest phpstan psalm composer mix swift xcodebuild flutter dart pod cmake " + "ninja meson ctest scons docker-compose buildah earthly").split(" "));
21023
+ });
21024
+
21025
+ // packages/workit-core/src/hooks/heavy-gate.ts
21026
+ function heavyGate(cwd, command, options) {
21027
+ const env = options.env ?? process.env;
21028
+ try {
21029
+ if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
21030
+ return { kind: "none" };
21031
+ return heavyPlan(command, {
21032
+ cwd,
21033
+ env,
21034
+ dialect: options.dialect ?? "posix",
21035
+ rules: rulesFor(options.host, cwd, env),
21036
+ bypass: options.permissionMode === "bypassPermissions"
21037
+ });
21038
+ } catch {
21039
+ return { kind: "none" };
21040
+ }
21041
+ }
21042
+ var rulesFor = (host, cwd, env) => {
21043
+ switch (host) {
21044
+ case "claude_code":
21045
+ return claudeRules(cwd, env);
21046
+ case "opencode":
21047
+ return opencodeRules(cwd, env);
21048
+ case "codex_cli":
21049
+ case "codex_desktop":
21050
+ return codexRules(cwd, env);
21051
+ case "cursor":
21052
+ return cursorRules(cwd, env);
21053
+ case "pi":
21054
+ return null;
21055
+ }
21056
+ }, rewriteNote = (plan) => `workit: this heavy command (${plan.reason}) runs under the machine-wide heavy-check lease as \`${plan.command}\`: it waits while another heavy check holds the lease (\`workit lock status\` shows who). Run heavy commands that way yourself; \`workit help lock\` explains the lease.`, heavyDenial = (original, plan) => ({
21057
+ kind: "deny",
21058
+ reason: `heavy_command: \`${original.trim()}\` is heavy (${plan.reason}) and must run under the machine-wide heavy-check lease, so parallel agents cannot exhaust this machine's memory and CPU. Run exactly: ${plan.command}`,
21059
+ unblock: plan.command
21060
+ }), splitDenial = (plan) => ({
21061
+ kind: "deny",
21062
+ reason: plan.reason,
21063
+ unblock: plan.unblock
21064
+ });
21065
+ var init_heavy_gate = __esm(() => {
21066
+ init_heavy_rules();
21067
+ init_heavy();
21068
+ init_heavy_words();
21069
+ });
21070
+
21071
+ // packages/workit-core/src/core/live-session.ts
21072
+ import { spawnSync as spawnSync13 } from "node:child_process";
21073
+ import { createHash as createHash15 } from "node:crypto";
21074
+ import fs20 from "node:fs";
21075
+ import os9 from "node:os";
21076
+ import path25 from "node:path";
20156
21077
  var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session, familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, ""), processAlive = (pid) => {
20157
21078
  try {
20158
21079
  process.kill(pid, 0);
@@ -20163,9 +21084,9 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20163
21084
  }, processInfo = (pid) => {
20164
21085
  try {
20165
21086
  if (liveProbe.platform === "linux") {
20166
- const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
21087
+ const stat = fs20.readFileSync(`/proc/${pid}/stat`, "utf8");
20167
21088
  const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20168
- const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
21089
+ const command = fs20.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20169
21090
  return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20170
21091
  }
20171
21092
  if (liveProbe.platform === "win32")
@@ -20210,32 +21131,32 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20210
21131
  pid: () => hostPid(transport)
20211
21132
  };
20212
21133
  }, checkoutOf = (target) => {
20213
- let dir = path22.resolve(target);
20214
- while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20215
- dir = path22.dirname(dir);
20216
- if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20217
- dir = path22.dirname(dir);
20218
- const parts = dir.split(path22.sep);
21134
+ let dir = path25.resolve(target);
21135
+ while (!fs20.existsSync(dir) && path25.dirname(dir) !== dir)
21136
+ dir = path25.dirname(dir);
21137
+ if (fs20.existsSync(dir) && !fs20.statSync(dir).isDirectory())
21138
+ dir = path25.dirname(dir);
21139
+ const parts = dir.split(path25.sep);
20219
21140
  const dotGit = parts.lastIndexOf(".git");
20220
21141
  if (dotGit > 0)
20221
- dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
21142
+ dir = parts.slice(0, dotGit).join(path25.sep) || path25.sep;
20222
21143
  const location = resolveStore(dir);
20223
21144
  if (location instanceof Error || !location.shared || !location.top)
20224
21145
  return null;
20225
21146
  let top = location.top;
20226
21147
  try {
20227
- top = fs17.realpathSync.native(top);
21148
+ top = fs20.realpathSync.native(top);
20228
21149
  } catch {}
20229
21150
  return top;
20230
- }, markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`), checkoutKey = (top, platform = process.platform) => {
20231
- let key = path22.resolve(top);
21151
+ }, markerFile2 = (top) => path25.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`), checkoutKey = (top, platform = process.platform) => {
21152
+ let key = path25.resolve(top);
20232
21153
  try {
20233
- key = fs17.realpathSync.native(key);
21154
+ key = fs20.realpathSync.native(key);
20234
21155
  } catch {}
20235
21156
  return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20236
21157
  }, readMarker = (file) => {
20237
21158
  try {
20238
- const value = JSON.parse(fs17.readFileSync(file, "utf8"));
21159
+ const value = JSON.parse(fs20.readFileSync(file, "utf8"));
20239
21160
  if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20240
21161
  return null;
20241
21162
  return {
@@ -20256,7 +21177,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20256
21177
  const age = ageOf2(marker, deps.now ?? new Date);
20257
21178
  if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20258
21179
  return false;
20259
- if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
21180
+ if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
20260
21181
  return true;
20261
21182
  if (!(deps.alive ?? processAlive)(marker.pid))
20262
21183
  return false;
@@ -20282,7 +21203,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20282
21203
  const marker = readMarker(file);
20283
21204
  if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20284
21205
  return null;
20285
- if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
21206
+ if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
20286
21207
  return null;
20287
21208
  return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20288
21209
  } catch {
@@ -20295,7 +21216,7 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20295
21216
  return;
20296
21217
  const file = markerFile2(checkout);
20297
21218
  const now = deps.now ?? new Date;
20298
- const machine = deps.machine ?? os6.hostname();
21219
+ const machine = deps.machine ?? os9.hostname();
20299
21220
  const current = readMarker(file);
20300
21221
  if (current) {
20301
21222
  const age = ageOf2(current, now);
@@ -20316,10 +21237,10 @@ var LIVE_SESSION_TTL_MS, HEARTBEAT_EVERY_MS = 30000, liveProbe, leadOf = (sessio
20316
21237
  checkout,
20317
21238
  heartbeatAt: now.toISOString()
20318
21239
  };
20319
- fs17.mkdirSync(path22.dirname(file), { recursive: true });
21240
+ fs20.mkdirSync(path25.dirname(file), { recursive: true });
20320
21241
  const temp = `${file}.${process.pid}.tmp`;
20321
- fs17.writeFileSync(temp, JSON.stringify(marker));
20322
- fs17.renameSync(temp, file);
21242
+ fs20.writeFileSync(temp, JSON.stringify(marker));
21243
+ fs20.renameSync(temp, file);
20323
21244
  } catch {}
20324
21245
  };
20325
21246
  var init_live_session = __esm(() => {
@@ -20338,8 +21259,8 @@ var init_live_session = __esm(() => {
20338
21259
 
20339
21260
  // packages/workit-core/src/hooks/write-gate.ts
20340
21261
  import { realpathSync as realpathSync5 } from "node:fs";
20341
- import os7 from "node:os";
20342
- import path23 from "node:path";
21262
+ import os10 from "node:os";
21263
+ import path26 from "node:path";
20343
21264
  function writeGate(cwd, paths, owner, deps) {
20344
21265
  if (!owner)
20345
21266
  return beforeWriteGate(cwd, paths);
@@ -20359,7 +21280,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20359
21280
  try {
20360
21281
  targets = [
20361
21282
  ...segmentTargets(cwd, command, dialect),
20362
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
21283
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path26.join(dir, DIR_TARGET))
20363
21284
  ];
20364
21285
  } catch {
20365
21286
  targets = [];
@@ -20397,24 +21318,37 @@ function beforeWriteGate(cwd, paths) {
20397
21318
  }
20398
21319
  var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20399
21320
  let rest = "";
20400
- for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
21321
+ for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
20401
21322
  try {
20402
- return path23.join(realpathSync5(dir), rest);
21323
+ return path26.join(realpathSync5(dir), rest);
20403
21324
  } catch {
20404
- if (path23.dirname(dir) === dir)
20405
- return path23.resolve(file);
20406
- rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
21325
+ if (path26.dirname(dir) === dir)
21326
+ return path26.resolve(file);
21327
+ rest = rest ? path26.join(path26.basename(dir), rest) : path26.basename(dir);
20407
21328
  }
20408
21329
  }
20409
21330
  }, exempt = (cwd, root, file, plans) => {
20410
21331
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20411
- const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20412
- if (relative.startsWith("..") || path23.isAbsolute(relative))
21332
+ const relative = path26.relative(fold(canonical2(root)), fold(canonical2(path26.resolve(cwd, file))));
21333
+ if (relative.startsWith("..") || path26.isAbsolute(relative))
20413
21334
  return true;
20414
- const posix = relative.split(path23.sep).join("/");
21335
+ const posix = relative.split(path26.sep).join("/");
20415
21336
  const segments = posix.split("/");
20416
21337
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20417
- }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), editsOf = (input) => {
21338
+ }, PREFIXES2, operands = (words) => words.filter((item) => !item.startsWith("-")), SCRIPT_SHELLS, wrappedScript = (input) => {
21339
+ let words = input;
21340
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
21341
+ words = words.slice(1);
21342
+ const tail = workitCheckTail(words);
21343
+ if (!tail)
21344
+ return null;
21345
+ if ("script" in tail)
21346
+ return tail.script;
21347
+ const [shell, flag, script] = tail.argv;
21348
+ if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
21349
+ return script ?? null;
21350
+ return tail.argv.map(quoteWord2).join(" ");
21351
+ }, editsOf = (input) => {
20418
21352
  let words = input;
20419
21353
  while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
20420
21354
  words = words.slice(1);
@@ -20489,10 +21423,18 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20489
21423
  default:
20490
21424
  return [];
20491
21425
  }
20492
- }, shellWrites = (command) => {
21426
+ }, shellWrites = (command, depth = 0) => {
20493
21427
  const targets = [];
20494
21428
  let unknown = false;
20495
21429
  for (const segment of segmentsOf(command)) {
21430
+ const inner = depth < 4 ? wrappedScript(segment.words) : null;
21431
+ if (inner !== null) {
21432
+ const nested = shellWrites(inner, depth + 1);
21433
+ if (nested.writes && nested.targets === null)
21434
+ unknown = true;
21435
+ else if (nested.targets)
21436
+ targets.push(...nested.targets);
21437
+ }
20496
21438
  targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
20497
21439
  const edits = editsOf(segment.words);
20498
21440
  if (edits === null)
@@ -20503,7 +21445,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20503
21445
  if (!unknown && targets.length === 0)
20504
21446
  return { writes: false, targets: null };
20505
21447
  return { writes: true, targets: unknown ? null : targets };
20506
- }, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
21448
+ }, cache2, planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path26.resolve(root, file)) ?? "-").join("\x00"), gateFor = (cwd) => {
20507
21449
  const store = new TaskStore(cwd);
20508
21450
  const listed = store.listTaskIndex();
20509
21451
  if (!listed.ok)
@@ -20556,19 +21498,19 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20556
21498
  return [];
20557
21499
  if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20558
21500
  return [];
20559
- return [path23.resolve(cwd, invocation.dir ?? ".")];
21501
+ return [path26.resolve(cwd, invocation.dir ?? ".")];
20560
21502
  });
20561
21503
  }, CD2, PUSHD2, POPD2, cdInto = (dir, words) => {
20562
21504
  const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20563
21505
  if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20564
21506
  return;
20565
- return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20566
- }, quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, segmentTargets = (cwd, command, dialect) => {
21507
+ return target === undefined ? os10.homedir() : path26.resolve(dir, target);
21508
+ }, quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`, segmentTargets = (cwd, command, dialect, depth = 0) => {
20567
21509
  const targets = [];
20568
21510
  let dir = cwd;
20569
21511
  let stack = [];
20570
21512
  const groups = [];
20571
- const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
21513
+ const at = (file) => path26.isAbsolute(file) ? file : dir === undefined ? null : path26.resolve(dir, file);
20572
21514
  for (const segment of segmentsOf(command, dialect)) {
20573
21515
  if (segment.group === "open") {
20574
21516
  groups.push({ dir, stack: [...stack] });
@@ -20596,6 +21538,9 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20596
21538
  dir = stack.length ? stack.pop() : undefined;
20597
21539
  continue;
20598
21540
  }
21541
+ const inner = depth < 4 ? wrappedScript(words) : null;
21542
+ if (inner !== null && dir !== undefined)
21543
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20599
21544
  for (const file of segment.redirects)
20600
21545
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20601
21546
  const resolved = at(file);
@@ -20605,15 +21550,15 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20605
21550
  const edits = editsOf(segment.words);
20606
21551
  if (edits === null) {
20607
21552
  if (dir !== undefined)
20608
- targets.push(path23.join(dir, DIR_TARGET));
21553
+ targets.push(path26.join(dir, DIR_TARGET));
20609
21554
  } else
20610
21555
  for (const file of edits) {
20611
21556
  const resolved = at(file);
20612
21557
  if (resolved)
20613
21558
  targets.push(resolved);
20614
21559
  }
20615
- if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
20616
- targets.push(path23.join(dir, DIR_TARGET));
21560
+ 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), "")))
21561
+ targets.push(path26.join(dir, DIR_TARGET));
20617
21562
  }
20618
21563
  return targets;
20619
21564
  }, overridden = (command, dialect) => {
@@ -20636,7 +21581,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20636
21581
  }, guarded = (targets) => {
20637
21582
  const seen = new Set;
20638
21583
  return targets.filter((target) => {
20639
- const dir = path23.dirname(target);
21584
+ const dir = path26.dirname(target);
20640
21585
  if (seen.has(dir))
20641
21586
  return false;
20642
21587
  seen.add(dir);
@@ -20654,7 +21599,7 @@ var NONE3, CASE_INSENSITIVE, canonical2 = (file) => {
20654
21599
  }, claimAll = (owner, targets, deps) => {
20655
21600
  for (const target of guarded(targets))
20656
21601
  claimLiveSession(target, owner, deps);
20657
- }, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)], noteOverride = (cwd, owner, command) => {
21602
+ }, liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path26.resolve(cwd, file)) : [path26.join(cwd, DIR_TARGET)], noteOverride = (cwd, owner, command) => {
20658
21603
  try {
20659
21604
  if (!inWorkitWorkspace(cwd))
20660
21605
  return;
@@ -20678,6 +21623,7 @@ var init_write_gate = __esm(() => {
20678
21623
  NONE3 = { kind: "none" };
20679
21624
  CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20680
21625
  PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
21626
+ SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
20681
21627
  cache2 = new Map;
20682
21628
  MUTATING_GIT = new Set([
20683
21629
  "add",
@@ -21090,9 +22036,9 @@ var init_skill_nudge = __esm(() => {
21090
22036
 
21091
22037
  // packages/workit-core/src/hooks/hosts/fields.ts
21092
22038
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
21093
- import path24 from "node:path";
21094
- 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) => {
21095
- if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
22039
+ import path27 from "node:path";
22040
+ var isRecord6 = (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) => {
22041
+ if (!nonEmpty2(value) || !path27.isAbsolute(value) || !existsSync7(value))
21096
22042
  return null;
21097
22043
  try {
21098
22044
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -21118,7 +22064,7 @@ var isRecord4 = (value) => value !== null && typeof value === "object" && !Array
21118
22064
  const joined = argv.map(quoteArg).join(" ");
21119
22065
  return nonEmpty2(joined) ? joined : null;
21120
22066
  }, WRITE_TOOLS, isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toLowerCase().replace(/[_-]/g, "")), PATH_KEYS, PATCH_HEADER, writePaths = (input) => {
21121
- if (!isRecord4(input))
22067
+ if (!isRecord6(input))
21122
22068
  return [];
21123
22069
  const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
21124
22070
  for (const value of Object.values(input))
@@ -21200,10 +22146,32 @@ function handleHook(input, deps) {
21200
22146
  const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
21201
22147
  if (gate.kind === "deny")
21202
22148
  return gate;
22149
+ const plan = heavyGate(input.cwd, event.command, {
22150
+ host: input.host,
22151
+ dialect: event.dialect,
22152
+ permissionMode: input.permissionMode
22153
+ });
22154
+ if (plan.kind === "split" && canDeny)
22155
+ return splitDenial(plan);
22156
+ const rewrites = usable2(descriptor.shellPolicy.rewrite);
22157
+ if (plan.kind === "rewrite" && !rewrites && canDeny)
22158
+ return heavyDenial(event.command, plan);
22159
+ const lease = plan.kind === "rewrite" && rewrites ? plan : null;
22160
+ const aside = plan.kind === "passthrough" ? plan.note : null;
21203
22161
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
22162
+ const rewritten = (text) => lease ? {
22163
+ kind: "rewrite",
22164
+ command: lease.command,
22165
+ input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
22166
+ text
22167
+ } : NONE5;
21204
22168
  if (descriptor.transport !== "hook-process")
21205
- return NONE5;
21206
- return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
22169
+ return rewritten(null);
22170
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
22171
+ if (!lease)
22172
+ return nudged;
22173
+ return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
22174
+ `));
21207
22175
  }
21208
22176
  case "shell.post":
21209
22177
  if (usable2(descriptor.events["shell.post"].support))
@@ -21289,6 +22257,7 @@ var init_handle = __esm(() => {
21289
22257
  init_branch();
21290
22258
  init_workspaces();
21291
22259
  init_host_session();
22260
+ init_heavy_gate();
21292
22261
  init_policy();
21293
22262
  init_raw_git();
21294
22263
  init_skill_nudge();
@@ -21306,7 +22275,7 @@ var init_handle = __esm(() => {
21306
22275
  });
21307
22276
 
21308
22277
  // packages/workit-core/src/hooks/hosts/claude-code.ts
21309
- 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) => {
22278
+ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix", toolCommand = (value) => isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
21310
22279
  const toolUseId = optionalText(value.tool_use_id);
21311
22280
  switch (name) {
21312
22281
  case "SessionStart":
@@ -21333,19 +22302,28 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
21333
22302
  kind: "tool.pre",
21334
22303
  tool: value.tool_name,
21335
22304
  toolUseId,
21336
- skill: isRecord4(value.tool_input) ? optionalText(value.tool_input.skill) : null
22305
+ skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
21337
22306
  }
21338
22307
  };
21339
22308
  if (!SHELL_TOOLS.has(value.tool_name))
21340
22309
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
21341
22310
  const command = toolCommand(value);
21342
- 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}` };
22311
+ return command ? {
22312
+ ok: true,
22313
+ event: {
22314
+ kind: "shell.pre",
22315
+ command,
22316
+ toolUseId,
22317
+ dialect: dialectOf2(value),
22318
+ toolInput: value.tool_input
22319
+ }
22320
+ } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
21343
22321
  }
21344
22322
  case "PostToolUse": {
21345
22323
  const command = toolCommand(value);
21346
22324
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
21347
22325
  return { ok: false, error: "only shell PostToolUse is mapped" };
21348
- const response = isRecord4(value.tool_response) ? value.tool_response : {};
22326
+ const response = isRecord6(value.tool_response) ? value.tool_response : {};
21349
22327
  return {
21350
22328
  ok: true,
21351
22329
  event: {
@@ -21395,6 +22373,17 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, dialectOf2 = (value) =
21395
22373
  };
21396
22374
  }
21397
22375
  }, CONTEXT_EVENTS, render = (decision, native) => {
22376
+ if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
22377
+ return {
22378
+ json: {
22379
+ hookSpecificOutput: {
22380
+ hookEventName: "PreToolUse",
22381
+ updatedInput: decision.input,
22382
+ ...decision.text ? { additionalContext: decision.text } : {}
22383
+ }
22384
+ },
22385
+ exitCode: 0
22386
+ };
21398
22387
  if (decision.kind === "deny" && native === "PreToolUse")
21399
22388
  return {
21400
22389
  json: {
@@ -21438,7 +22427,12 @@ var init_claude_code = __esm(() => {
21438
22427
  "compact.pre": { support: "none", native: null },
21439
22428
  stop: { support: "native", native: "Stop" }
21440
22429
  },
21441
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22430
+ shellPolicy: {
22431
+ deny: "native",
22432
+ channel: "permissionDecision",
22433
+ failClosed: false,
22434
+ rewrite: "native"
22435
+ },
21442
22436
  context: {
21443
22437
  sessionStart: "native",
21444
22438
  perTurn: "native",
@@ -21529,14 +22523,14 @@ var init_claude_code = __esm(() => {
21529
22523
  claudeCodeAdapter = {
21530
22524
  descriptor: CLAUDE_CODE_DESCRIPTOR,
21531
22525
  parse(raw) {
21532
- const native = isRecord4(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22526
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
21533
22527
  const fail = (error) => ({
21534
22528
  ok: false,
21535
22529
  error,
21536
22530
  native,
21537
22531
  event: native ? EVENTS[native] : null
21538
22532
  });
21539
- if (!isRecord4(raw) || !native)
22533
+ if (!isRecord6(raw) || !native)
21540
22534
  return fail("hook_event_name is required");
21541
22535
  if (!nonEmpty2(raw.session_id))
21542
22536
  return fail("session_id is required");
@@ -21573,7 +22567,7 @@ function detectCodexSurface(env) {
21573
22567
  return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
21574
22568
  }
21575
22569
  var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase()), SOURCES2, parseCodexHookInput = (value) => {
21576
- if (!isRecord4(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
22570
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
21577
22571
  return { ok: false, error: "hook_event_name is required" };
21578
22572
  const event = value.hook_event_name;
21579
22573
  const cwd = existingDirectory(value.cwd);
@@ -21582,7 +22576,7 @@ var CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS
21582
22576
  const toolEvent = event === "PreToolUse" || event === "PostToolUse";
21583
22577
  if (toolEvent && !nonEmpty2(value.tool_name))
21584
22578
  return { ok: false, error: "tool_name is required" };
21585
- const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
22579
+ const command = isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
21586
22580
  if (toolEvent && isShellTool(value.tool_name) && !command)
21587
22581
  return { ok: false, error: "tool_input.command is required for shell tools" };
21588
22582
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -21708,7 +22702,12 @@ var init_codex = __esm(() => {
21708
22702
  "compact.pre": { support: "none", native: null },
21709
22703
  stop: { support: "native", native: "Stop" }
21710
22704
  },
21711
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22705
+ shellPolicy: {
22706
+ deny: "native",
22707
+ channel: "permissionDecision",
22708
+ failClosed: false,
22709
+ rewrite: "none"
22710
+ },
21712
22711
  context: {
21713
22712
  sessionStart: "native",
21714
22713
  perTurn: "native",
@@ -21804,7 +22803,7 @@ var init_codex = __esm(() => {
21804
22803
  const host = detectCodexSurface(env);
21805
22804
  const parsed = parseCodexHookInput(raw);
21806
22805
  if (!parsed.ok) {
21807
- const native = isRecord4(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22806
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
21808
22807
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
21809
22808
  }
21810
22809
  const input = parsed.data;
@@ -21832,10 +22831,10 @@ var init_codex = __esm(() => {
21832
22831
  });
21833
22832
 
21834
22833
  // packages/workit-core/src/hooks/hosts/cursor.ts
21835
- import fs18 from "node:fs";
21836
- import path25 from "node:path";
22834
+ import fs21 from "node:fs";
22835
+ import path28 from "node:path";
21837
22836
  var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
21838
- if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
22837
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
21839
22838
  return { ok: false, error: "hook_event_name is required" };
21840
22839
  const roots = value.workspace_roots;
21841
22840
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -21914,17 +22913,17 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
21914
22913
  };
21915
22914
  }
21916
22915
  }, TRANSCRIPT_TAIL_BYTES, lastAssistantText = (file) => {
21917
- if (!file || !path25.isAbsolute(file))
22916
+ if (!file || !path28.isAbsolute(file))
21918
22917
  return null;
21919
22918
  try {
21920
- const size = fs18.statSync(file).size;
22919
+ const size = fs21.statSync(file).size;
21921
22920
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21922
22921
  const buffer = Buffer.alloc(size - start);
21923
- const fd = fs18.openSync(file, "r");
22922
+ const fd = fs21.openSync(file, "r");
21924
22923
  try {
21925
- fs18.readSync(fd, buffer, 0, buffer.length, start);
22924
+ fs21.readSync(fd, buffer, 0, buffer.length, start);
21926
22925
  } finally {
21927
- fs18.closeSync(fd);
22926
+ fs21.closeSync(fd);
21928
22927
  }
21929
22928
  const lines = buffer.toString("utf8").split(`
21930
22929
  `);
@@ -21935,14 +22934,14 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
21935
22934
  } catch {
21936
22935
  continue;
21937
22936
  }
21938
- if (!isRecord4(entry) || entry.role !== "assistant")
22937
+ if (!isRecord6(entry) || entry.role !== "assistant")
21939
22938
  continue;
21940
- const content = isRecord4(entry.message) ? entry.message.content : entry.content;
22939
+ const content = isRecord6(entry.message) ? entry.message.content : entry.content;
21941
22940
  if (typeof content === "string")
21942
22941
  return content;
21943
22942
  if (!Array.isArray(content))
21944
22943
  return null;
21945
- const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
22944
+ const text = content.filter((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
21946
22945
  `);
21947
22946
  return text || null;
21948
22947
  }
@@ -21950,7 +22949,7 @@ var undocumented, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (valu
21950
22949
  } catch {
21951
22950
  return null;
21952
22951
  }
21953
- }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`), hookCwd = (input) => {
22952
+ }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path28.sep) ? root : `${root}${path28.sep}`), hookCwd = (input) => {
21954
22953
  const cwd = existingDirectory(input.cwd);
21955
22954
  if (cwd && input.hook_event_name === "beforeShellExecution")
21956
22955
  return cwd;
@@ -22004,7 +23003,7 @@ var init_cursor = __esm(() => {
22004
23003
  "compact.pre": { support: "partial", native: "preCompact" },
22005
23004
  stop: { support: "native", native: "stop" }
22006
23005
  },
22007
- shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
23006
+ shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
22008
23007
  context: {
22009
23008
  sessionStart: "native",
22010
23009
  perTurn: "none",
@@ -22103,7 +23102,7 @@ var init_cursor = __esm(() => {
22103
23102
  parse(raw) {
22104
23103
  const parsed = parseCursorHookInput(raw);
22105
23104
  if (!parsed.ok) {
22106
- const native = isRecord4(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
23105
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22107
23106
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
22108
23107
  }
22109
23108
  const input = parsed.data;
@@ -22175,6 +23174,7 @@ var init_pi = () => {};
22175
23174
  var init_hooks = __esm(async () => {
22176
23175
  init_context();
22177
23176
  init_policy();
23177
+ init_heavy_gate();
22178
23178
  init_write_gate();
22179
23179
  init_live_session();
22180
23180
  init_stop();