@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.
@@ -9366,6 +9366,7 @@ var workspaceConfigSchema = object({
9366
9366
  autoApprove: union([boolean2(), array(nonBlank)]).optional(),
9367
9367
  defaultEndpoint: unknown().optional(),
9368
9368
  verification: _enum(["self", "independent"]).optional(),
9369
+ delegation: unknown().optional(),
9369
9370
  trailers: boolean2().optional(),
9370
9371
  liveSessionGuard: boolean2().optional(),
9371
9372
  fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
@@ -15071,6 +15072,17 @@ import fs12 from "node:fs";
15071
15072
  import path16 from "node:path";
15072
15073
  var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
15073
15074
  var CHECKS_FILE = "workit.checks.json";
15075
+ var checksConfigFile = (root) => path16.join(root, CHECKS_FILE);
15076
+ function repoHeavyCommands(root) {
15077
+ try {
15078
+ const value = JSON.parse(fs12.readFileSync(checksConfigFile(root), "utf8"));
15079
+ const resources = isRecord3(value) ? value.resources : undefined;
15080
+ const list = isRecord3(resources) ? resources.heavyCommands : undefined;
15081
+ return Array.isArray(list) ? list.filter((item) => typeof item === "string") : [];
15082
+ } catch {
15083
+ return [];
15084
+ }
15085
+ }
15074
15086
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
15075
15087
  var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
15076
15088
  function splitCommand(command) {
@@ -15183,7 +15195,7 @@ function detectedChecks(root) {
15183
15195
  var GATES = ["testing", "verification"];
15184
15196
  function loadCheckConfig(cwd) {
15185
15197
  const root = checkRoot(cwd);
15186
- const file = path16.join(root, CHECKS_FILE);
15198
+ const file = checksConfigFile(root);
15187
15199
  const none = (error = null) => ({
15188
15200
  source: error ? CHECKS_FILE : "none",
15189
15201
  root,
@@ -15935,6 +15947,54 @@ function evaluateClosure(requestedOutcome, view) {
15935
15947
  requirementIds: evaluations.map((item) => item.requirementId)
15936
15948
  });
15937
15949
  }
15950
+ var BRIEF_TEXT = 250;
15951
+ var BRIEF_BLOCKERS = 3;
15952
+ var BRIEF_OPEN = 8;
15953
+ var clip = (value) => {
15954
+ const fits = (text) => Buffer.byteLength(JSON.stringify(text)) <= BRIEF_TEXT;
15955
+ if (fits(value))
15956
+ return value;
15957
+ let length = Math.min(value.length, BRIEF_TEXT);
15958
+ while (length > 0 && !fits(`${value.slice(0, length)}…`))
15959
+ length -= 1;
15960
+ return `${value.slice(0, length)}…`;
15961
+ };
15962
+ function boundTaskSummary(summary) {
15963
+ const rules = new Map((summary.policy?.requirements ?? []).map((item) => [item.id, item]));
15964
+ const unmet = summary.requirements.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
15965
+ const { progress, closure } = summary;
15966
+ const next = progress.nextAction;
15967
+ const open = unmet.slice(0, BRIEF_OPEN).map((item) => ({
15968
+ dimension: rules.get(item.requirementId)?.dimension ?? "verification",
15969
+ before: rules.get(item.requirementId)?.before ?? "close",
15970
+ status: item.status
15971
+ }));
15972
+ const brief = {
15973
+ id: summary.id,
15974
+ revision: summary.revision,
15975
+ workspaceRevision: summary.workspaceRevision,
15976
+ updatedAt: summary.updatedAt,
15977
+ objective: clip(summary.objective),
15978
+ status: summary.status,
15979
+ closure: closure && {
15980
+ outcome: closure.outcome,
15981
+ at: closure.at,
15982
+ summary: clip(closure.summary)
15983
+ },
15984
+ progress: {
15985
+ summary: clip(progress.summary),
15986
+ nextAction: next && clip(next),
15987
+ blockers: progress.blockers.slice(0, BRIEF_BLOCKERS).map((item) => ({
15988
+ reason: clip(item.reason),
15989
+ dependentAction: clip(item.dependentAction)
15990
+ }))
15991
+ },
15992
+ requirements: { total: summary.requirements.length, unmet: unmet.length, open },
15993
+ truncated: false
15994
+ };
15995
+ brief.truncated = JSON.stringify(brief).includes("…") || progress.blockers.length > BRIEF_BLOCKERS || unmet.length > BRIEF_OPEN;
15996
+ return brief;
15997
+ }
15938
15998
 
15939
15999
  // packages/workit-core/src/core/methods.ts
15940
16000
  var METHODS = {
@@ -16732,6 +16792,15 @@ class WorkitCore {
16732
16792
  if (!parsed.ok)
16733
16793
  return parsed;
16734
16794
  const input = parsed.data;
16795
+ if (this.context.readOnlyChild) {
16796
+ if (input.action !== "inspect")
16797
+ return failure2("permission_denied", "child sessions can only inspect the task summary");
16798
+ const task = this.store.readTask(input.taskId);
16799
+ if (!task.ok)
16800
+ return task;
16801
+ const summary = this.summary(task.data);
16802
+ return summary.ok ? { ...summary, data: boundTaskSummary(summary.data) } : summary;
16803
+ }
16735
16804
  if ((this.context.workerId ?? null) !== null) {
16736
16805
  if (input.action !== "inspect" && input.action !== "list")
16737
16806
  return failure2("permission_denied", "helpers cannot control task lifecycle or scope");
@@ -19175,7 +19244,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
19175
19244
  return found;
19176
19245
  }
19177
19246
  var RECENT = new Map;
19178
- function walk2(command, dialect, depth) {
19247
+ function commandLines(command, dialect = "posix") {
19248
+ const lines = [];
19249
+ try {
19250
+ walk2(command, dialect, 0, lines);
19251
+ } catch (error) {
19252
+ throw error instanceof ShellAnalysisError ? error : new ShellAnalysisError(error instanceof Error ? error.message : String(error));
19253
+ }
19254
+ return lines;
19255
+ }
19256
+ function walk2(command, dialect, depth, seen) {
19179
19257
  const found = [];
19180
19258
  let dir = null;
19181
19259
  const groups = [];
@@ -19186,7 +19264,8 @@ function walk2(command, dialect, depth) {
19186
19264
  const nested = (script, inner) => {
19187
19265
  if (!script || depth >= MAX_DEPTH)
19188
19266
  return;
19189
- for (const invocation of rawInvocations(script, inner, depth + 1))
19267
+ const inside = seen ? walk2(script, inner, depth + 1, seen) : rawInvocations(script, inner, depth + 1);
19268
+ for (const invocation of inside)
19190
19269
  found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
19191
19270
  };
19192
19271
  const visit = (input, stdin, hops) => {
@@ -19198,11 +19277,17 @@ function walk2(command, dialect, depth) {
19198
19277
  const name = commandName(words[0]);
19199
19278
  if (!name)
19200
19279
  return;
19280
+ seen?.push(words);
19281
+ if (name === "-c" || name === "--command")
19282
+ return nested(words[1], "posix");
19201
19283
  const runs = (lines) => {
19202
19284
  if (hops < MAX_DEPTH)
19203
19285
  for (const next of lines)
19204
19286
  visit(next, undefined, hops + 1);
19205
19287
  };
19288
+ const wrapped = seen ? null : workitCheckTail(words);
19289
+ if (wrapped)
19290
+ return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
19206
19291
  if (CD.has(name)) {
19207
19292
  const target = cdTarget(words);
19208
19293
  dir = target === null ? os5.homedir() : joinDir(dir, target);
@@ -19293,6 +19378,51 @@ var LAUNCHERS = new Set([
19293
19378
  "command"
19294
19379
  ]);
19295
19380
  var WORKIT_BIN = /(?:^|[\\/])workit(?:\.exe)?$|^@brainervirus\/workit-cli$/;
19381
+ var WORKIT_PACKAGE = /^@brainervirus\/workit-cli(?:@\S*)?$/;
19382
+ var PACKAGE_RUNNERS = new Set(["npx", "bunx", "pnpx"]);
19383
+ function workitCheckTail(words) {
19384
+ let at = 0;
19385
+ if (PACKAGE_RUNNERS.has(commandName(words[0]))) {
19386
+ at = words.findIndex((word, index) => index > 0 && !word.startsWith("-"));
19387
+ if (at < 0 || !WORKIT_PACKAGE.test(words[at]))
19388
+ return null;
19389
+ } else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
19390
+ return null;
19391
+ const args = words.slice(at + 1);
19392
+ if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
19393
+ return null;
19394
+ const split = args.indexOf("--");
19395
+ const tail = split < 0 ? [] : args.slice(split + 1);
19396
+ if (!tail.length)
19397
+ return null;
19398
+ return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
19399
+ }
19400
+ function simpleCommand(command, dialect = "posix", depth = 0) {
19401
+ let segments;
19402
+ try {
19403
+ segments = segmentsOf(command, dialect);
19404
+ } catch {
19405
+ return null;
19406
+ }
19407
+ if (segments.length !== 1)
19408
+ return null;
19409
+ const [segment] = segments;
19410
+ if (segment.group || segment.pipe || segment.unanalyzable || segment.redirects.length || segment.subs?.length || segment.stdin?.length)
19411
+ return null;
19412
+ const { words } = stripPrefixes(segment.words);
19413
+ const name = commandName(words[0]);
19414
+ if (!name || depth >= MAX_DEPTH)
19415
+ return null;
19416
+ if (SHELLS.has(name)) {
19417
+ const script = shellScript(words, undefined);
19418
+ return script ? simpleCommand(script, "posix", depth + 1) : null;
19419
+ }
19420
+ if (name === "-c" || name === "--command")
19421
+ return words[1] ? simpleCommand(words[1], "posix", depth + 1) : null;
19422
+ if (EVALS.has(name) || POWERSHELLS.has(name) || ["xargs", "find", "parallel", "script", "cmd", "source", "."].includes(name))
19423
+ return null;
19424
+ return words;
19425
+ }
19296
19426
  function workitInvocations(command, dialect = "posix", depth = 0) {
19297
19427
  const found = [];
19298
19428
  for (const { words } of segmentsOf(command, dialect)) {
@@ -20320,17 +20450,827 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20320
20450
  }
20321
20451
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
20322
20452
  };
20323
- // packages/workit-core/src/hooks/write-gate.ts
20324
- import { realpathSync as realpathSync5 } from "node:fs";
20453
+ // packages/workit-core/src/core/heavy-rules.ts
20454
+ import fs17 from "node:fs";
20455
+ import os6 from "node:os";
20456
+ import path22 from "node:path";
20457
+ var EMPTY = () => ({ deny: [], allow: [] });
20458
+ var readText2 = (file) => {
20459
+ try {
20460
+ return fs17.readFileSync(file, "utf8");
20461
+ } catch (error) {
20462
+ if (error.code === "ENOENT")
20463
+ return null;
20464
+ throw error;
20465
+ }
20466
+ };
20467
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
20468
+ function parseJsonc(text) {
20469
+ let out = "";
20470
+ for (let index = 0;index < text.length; index += 1) {
20471
+ const char = text[index];
20472
+ if (char === '"') {
20473
+ const end = /"(?:[^"\\]|\\.)*"/y;
20474
+ end.lastIndex = index;
20475
+ const match = end.exec(text);
20476
+ if (!match)
20477
+ throw new Error("unterminated string");
20478
+ out += match[0];
20479
+ index += match[0].length - 1;
20480
+ } else if (char === "/" && text[index + 1] === "/") {
20481
+ while (index < text.length && text[index] !== `
20482
+ `)
20483
+ index += 1;
20484
+ out += `
20485
+ `;
20486
+ } else if (char === "/" && text[index + 1] === "*") {
20487
+ const close = text.indexOf("*/", index + 2);
20488
+ if (close < 0)
20489
+ throw new Error("unterminated comment");
20490
+ index = close + 1;
20491
+ } else
20492
+ out += char;
20493
+ }
20494
+ return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
20495
+ }
20496
+ function gitTop(cwd) {
20497
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
20498
+ if (fs17.existsSync(path22.join(dir, ".git")))
20499
+ return dir;
20500
+ if (path22.dirname(dir) === dir)
20501
+ return null;
20502
+ }
20503
+ }
20504
+ var projectDirs = (cwd) => {
20505
+ const top = gitTop(cwd);
20506
+ const dirs = [];
20507
+ for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
20508
+ dirs.push(dir);
20509
+ if (!top || dir === top || path22.dirname(dir) === dir)
20510
+ return dirs;
20511
+ }
20512
+ };
20513
+ var homeOf = (env) => env.HOME || os6.homedir();
20514
+ var CLAUDE_MANAGED = {
20515
+ linux: "/etc/claude-code/managed-settings.json",
20516
+ darwin: "/Library/Application Support/ClaudeCode/managed-settings.json",
20517
+ win32: "C:\\Program Files\\ClaudeCode\\managed-settings.json"
20518
+ };
20519
+ function claudeSettingsFiles(cwd, env, platform = process.platform) {
20520
+ const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
20521
+ const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
20522
+ return [
20523
+ ...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
20524
+ path22.join(user, "settings.json"),
20525
+ path22.join(project, ".claude", "settings.json"),
20526
+ path22.join(project, ".claude", "settings.local.json")
20527
+ ];
20528
+ }
20529
+ var bashPattern = (entry) => {
20530
+ const match = /^Bash(?:\((.*)\))?$/su.exec(entry.trim());
20531
+ return match ? match[1] ?? "" : null;
20532
+ };
20533
+ function claudeRules(cwd, env = process.env) {
20534
+ const rules = EMPTY();
20535
+ try {
20536
+ for (const file of claudeSettingsFiles(cwd, env)) {
20537
+ const text = readText2(file);
20538
+ if (text === null)
20539
+ continue;
20540
+ const value = JSON.parse(text);
20541
+ if (!isRecord4(value))
20542
+ return "unreadable";
20543
+ const permissions = value.permissions;
20544
+ if (permissions === undefined)
20545
+ continue;
20546
+ if (!isRecord4(permissions))
20547
+ return "unreadable";
20548
+ for (const [key, bucket] of [
20549
+ ["deny", rules.deny],
20550
+ ["ask", rules.deny],
20551
+ ["allow", rules.allow]
20552
+ ]) {
20553
+ const list = permissions[key];
20554
+ if (list === undefined)
20555
+ continue;
20556
+ if (!Array.isArray(list))
20557
+ return "unreadable";
20558
+ for (const entry of list) {
20559
+ if (typeof entry !== "string")
20560
+ return "unreadable";
20561
+ const pattern = bashPattern(entry);
20562
+ if (pattern !== null)
20563
+ bucket.push(pattern);
20564
+ }
20565
+ }
20566
+ }
20567
+ } catch {
20568
+ return "unreadable";
20569
+ }
20570
+ return rules;
20571
+ }
20572
+ var OPENCODE_NAMES = ["opencode.json", "opencode.jsonc"];
20573
+ var opencodePermission = (permission, rules) => {
20574
+ if (permission === undefined)
20575
+ return true;
20576
+ if (typeof permission === "string") {
20577
+ (permission === "allow" ? rules.allow : rules.deny).push("");
20578
+ return ["allow", "ask", "deny"].includes(permission);
20579
+ }
20580
+ if (!isRecord4(permission))
20581
+ return false;
20582
+ for (const key of ["bash", "shell"]) {
20583
+ const value = permission[key];
20584
+ if (value === undefined)
20585
+ continue;
20586
+ if (typeof value === "string") {
20587
+ if (!["allow", "ask", "deny"].includes(value))
20588
+ return false;
20589
+ (value === "allow" ? rules.allow : rules.deny).push("");
20590
+ continue;
20591
+ }
20592
+ if (!isRecord4(value))
20593
+ return false;
20594
+ for (const [pattern, action] of Object.entries(value)) {
20595
+ if (action !== "allow" && action !== "ask" && action !== "deny")
20596
+ return false;
20597
+ (action === "allow" ? rules.allow : rules.deny).push(pattern);
20598
+ }
20599
+ }
20600
+ return true;
20601
+ };
20602
+ var opencodeConfig = (value, rules) => {
20603
+ if (!isRecord4(value))
20604
+ return false;
20605
+ if (!opencodePermission(value.permission, rules))
20606
+ return false;
20607
+ if (value.agent === undefined)
20608
+ return true;
20609
+ if (!isRecord4(value.agent))
20610
+ return false;
20611
+ return Object.values(value.agent).every((agent) => isRecord4(agent) && opencodePermission(agent.permission, rules));
20612
+ };
20613
+ function opencodeRules(cwd, env = process.env) {
20614
+ const rules = EMPTY();
20615
+ try {
20616
+ const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
20617
+ const files = [
20618
+ ...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
20619
+ ...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
20620
+ ...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
20621
+ ];
20622
+ for (const file of files) {
20623
+ const text = readText2(file);
20624
+ if (text !== null && !opencodeConfig(parseJsonc(text), rules))
20625
+ return "unreadable";
20626
+ }
20627
+ if (env.OPENCODE_CONFIG_CONTENT && !opencodeConfig(parseJsonc(env.OPENCODE_CONFIG_CONTENT), rules))
20628
+ return "unreadable";
20629
+ } catch {
20630
+ return "unreadable";
20631
+ }
20632
+ return rules;
20633
+ }
20634
+ var codexRule = (body) => {
20635
+ const open = /\bpattern\s*=\s*\[/u.exec(body);
20636
+ if (!open)
20637
+ return null;
20638
+ const words = [];
20639
+ let index = open.index + open[0].length;
20640
+ for (;; ) {
20641
+ while (/[\s,]/u.test(body[index] ?? ""))
20642
+ index += 1;
20643
+ if (body[index] === "]")
20644
+ break;
20645
+ if (body[index] !== '"')
20646
+ return null;
20647
+ const literal = /"((?:[^"\\]|\\.)*)"/y;
20648
+ literal.lastIndex = index;
20649
+ const match = literal.exec(body);
20650
+ if (!match)
20651
+ return null;
20652
+ words.push(JSON.parse(`"${match[1]}"`));
20653
+ index = literal.lastIndex;
20654
+ }
20655
+ const decision = /\bdecision\s*=\s*"(\w+)"/u.exec(body)?.[1];
20656
+ if (!words.length || !decision || !["allow", "prompt", "forbidden"].includes(decision))
20657
+ return null;
20658
+ return { pattern: words.join(" "), decision };
20659
+ };
20660
+ function codexRules(cwd, env = process.env) {
20661
+ const rules = EMPTY();
20662
+ try {
20663
+ const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
20664
+ const top = gitTop(cwd) ?? cwd;
20665
+ for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
20666
+ let files;
20667
+ try {
20668
+ files = fs17.readdirSync(dir).filter((name) => name.endsWith(".rules"));
20669
+ } catch (error) {
20670
+ if (error.code === "ENOENT")
20671
+ continue;
20672
+ return "unreadable";
20673
+ }
20674
+ for (const name of files) {
20675
+ const text = readText2(path22.join(dir, name)) ?? "";
20676
+ for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
20677
+ const rule = codexRule(part);
20678
+ if (!rule)
20679
+ return "unreadable";
20680
+ (rule.decision === "allow" ? rules.allow : rules.deny).push(rule.pattern);
20681
+ }
20682
+ }
20683
+ }
20684
+ } catch {
20685
+ return "unreadable";
20686
+ }
20687
+ return rules;
20688
+ }
20689
+ function cursorRules(cwd, env = process.env) {
20690
+ const rules = EMPTY();
20691
+ try {
20692
+ const top = gitTop(cwd) ?? cwd;
20693
+ for (const file of [
20694
+ path22.join(homeOf(env), ".cursor", "cli-config.json"),
20695
+ path22.join(top, ".cursor", "cli.json")
20696
+ ]) {
20697
+ const text = readText2(file);
20698
+ if (text === null)
20699
+ continue;
20700
+ const value = JSON.parse(text);
20701
+ if (!isRecord4(value))
20702
+ return "unreadable";
20703
+ if (value.permissions === undefined)
20704
+ continue;
20705
+ if (!isRecord4(value.permissions))
20706
+ return "unreadable";
20707
+ for (const [key, bucket] of [
20708
+ ["deny", rules.deny],
20709
+ ["allow", rules.allow]
20710
+ ]) {
20711
+ const list = value.permissions[key];
20712
+ if (list === undefined)
20713
+ continue;
20714
+ if (!Array.isArray(list))
20715
+ return "unreadable";
20716
+ for (const entry of list) {
20717
+ if (typeof entry !== "string")
20718
+ return "unreadable";
20719
+ const match = /^Shell(?:\((.*)\))?$/su.exec(entry.trim());
20720
+ if (match)
20721
+ bucket.push(match[1] ?? "");
20722
+ }
20723
+ }
20724
+ }
20725
+ } catch {
20726
+ return "unreadable";
20727
+ }
20728
+ return rules;
20729
+ }
20730
+ var baseName = (word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "");
20731
+ var firstToken = (pattern) => pattern.trim().split(/[:\s]/u)[0] ?? "";
20732
+ function names(pattern, words) {
20733
+ const token = firstToken(pattern);
20734
+ if (!token || token === "*" || token.includes("*"))
20735
+ return true;
20736
+ const wanted = baseName(token);
20737
+ return words.some((word) => baseName(word) === wanted);
20738
+ }
20739
+ var literalPrefix = (pattern) => pattern.trim().replace(/(?::\*| \*|\*)$/u, "");
20740
+ var coversWrapper = (pattern) => {
20741
+ const token = firstToken(pattern);
20742
+ if (!token || token.includes("*"))
20743
+ return true;
20744
+ const words = literalPrefix(pattern).split(/\s+/u);
20745
+ if (baseName(words[0]) !== "workit")
20746
+ return false;
20747
+ return ["check", "--heavy"].slice(0, words.length - 1).every((word, at) => {
20748
+ const given = words[at + 1];
20749
+ return given === word || word.startsWith(given);
20750
+ });
20751
+ };
20752
+ var coversCommand = (pattern, command) => {
20753
+ const prefix = literalPrefix(pattern);
20754
+ if (!prefix)
20755
+ return true;
20756
+ return !prefix.includes("*") && command.trim().startsWith(prefix);
20757
+ };
20758
+ function wrapAllowed(read, words, command, options = {}) {
20759
+ if (read === "unreadable")
20760
+ return { ok: false, why: "named" };
20761
+ if (read.deny.some((pattern) => names(pattern, words)))
20762
+ return { ok: false, why: "named" };
20763
+ if (!options.bypass && read.allow.some(coversWrapper) && !read.allow.some((pattern) => coversCommand(pattern, command)))
20764
+ return { ok: false, why: "allowlisted" };
20765
+ return { ok: true };
20766
+ }
20767
+
20768
+ // packages/workit-core/src/core/heavy.ts
20769
+ import fs18 from "node:fs";
20325
20770
  import os7 from "node:os";
20326
20771
  import path23 from "node:path";
20772
+ function stateDir(env, platform = process.platform) {
20773
+ if (env.WORKFLOW_TOOLKIT_STATE)
20774
+ return env.WORKFLOW_TOOLKIT_STATE;
20775
+ if (env.XDG_STATE_HOME)
20776
+ return path23.join(env.XDG_STATE_HOME, "workit");
20777
+ const home = env.HOME || os7.homedir();
20778
+ if (platform === "darwin")
20779
+ return path23.join(home, "Library", "Application Support", "workit");
20780
+ if (platform === "win32")
20781
+ return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
20782
+ return path23.join(home, ".local", "state", "workit");
20783
+ }
20784
+ var HEAVY_COMMANDS_FILE = "heavy-commands.json";
20785
+ var heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE);
20786
+ var 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;
20787
+ var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
20788
+ var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
20789
+ var WATCH_SCRIPT = /(?:^|[:_.-])(?:watch|dev|serve|start|storybook)(?:$|[:_.-])/iu;
20790
+ var sub = (...names) => new Set(names);
20791
+ var TOOLS = {
20792
+ jest: "any",
20793
+ vitest: "any",
20794
+ mocha: "any",
20795
+ ava: "any",
20796
+ tap: "any",
20797
+ karma: "any",
20798
+ jasmine: "any",
20799
+ nyc: "any",
20800
+ c8: "any",
20801
+ tsc: "any",
20802
+ "vue-tsc": "any",
20803
+ "svelte-check": "any",
20804
+ eslint: "any",
20805
+ stylelint: "any",
20806
+ tslint: "any",
20807
+ webpack: "any",
20808
+ "webpack-cli": "any",
20809
+ rollup: "any",
20810
+ parcel: "any",
20811
+ tsup: "any",
20812
+ vite: sub("build", "optimize"),
20813
+ next: sub("build", "lint"),
20814
+ nuxt: sub("build", "generate"),
20815
+ nuxi: sub("build", "generate"),
20816
+ astro: sub("build", "check"),
20817
+ ng: sub("build", "b", "test", "t", "e2e", "lint"),
20818
+ "react-scripts": sub("build", "test"),
20819
+ playwright: sub("test", "install"),
20820
+ cypress: sub("run"),
20821
+ wdio: "any",
20822
+ testcafe: "any",
20823
+ turbo: "any",
20824
+ lerna: sub("run", "exec", "bootstrap"),
20825
+ "sonar-scanner": "any",
20826
+ sonarless: sub("scan"),
20827
+ "sonarless-mcp": sub("scan"),
20828
+ go: sub("test", "build", "vet", "install", "generate"),
20829
+ "golangci-lint": sub("run"),
20830
+ gotestsum: "any",
20831
+ staticcheck: "any",
20832
+ cargo: sub("build", "b", "test", "t", "check", "c", "clippy", "bench", "nextest", "doc", "install", "tarpaulin", "llvm-cov"),
20833
+ mvn: "any",
20834
+ mvnw: "any",
20835
+ gradle: "any",
20836
+ gradlew: "any",
20837
+ ant: "any",
20838
+ sbt: "any",
20839
+ bazel: sub("build", "test", "run", "coverage"),
20840
+ bazelisk: sub("build", "test", "run", "coverage"),
20841
+ buck2: sub("build", "test", "run"),
20842
+ dotnet: sub("build", "test", "publish", "restore", "pack", "msbuild"),
20843
+ msbuild: "any",
20844
+ pytest: "any",
20845
+ "py.test": "any",
20846
+ tox: "any",
20847
+ nox: "any",
20848
+ mypy: "any",
20849
+ pyright: "any",
20850
+ pylint: "any",
20851
+ coverage: sub("run"),
20852
+ maturin: sub("build", "develop"),
20853
+ pip: sub("install", "wheel"),
20854
+ pip3: sub("install", "wheel"),
20855
+ rspec: "any",
20856
+ rails: sub("test", "assets:precompile"),
20857
+ phpunit: "any",
20858
+ pest: "any",
20859
+ phpstan: "any",
20860
+ psalm: "any",
20861
+ composer: sub("install", "update", "require", "test"),
20862
+ mix: sub("test", "compile", "deps.get", "dialyzer", "credo"),
20863
+ swift: sub("build", "test"),
20864
+ xcodebuild: "any",
20865
+ flutter: sub("build", "test"),
20866
+ dart: sub("test", "compile"),
20867
+ pod: sub("install"),
20868
+ cmake: sub("--build"),
20869
+ ninja: "any",
20870
+ meson: sub("compile", "test"),
20871
+ ctest: "any",
20872
+ scons: "any",
20873
+ "docker-compose": sub("build"),
20874
+ buildah: sub("bud", "build"),
20875
+ earthly: "any"
20876
+ };
20877
+ var PM_VALUES = new Set([
20878
+ "--prefix",
20879
+ "-w",
20880
+ "--workspace",
20881
+ "-C",
20882
+ "--dir",
20883
+ "--filter",
20884
+ "-F",
20885
+ "--cwd",
20886
+ "-p",
20887
+ "--package",
20888
+ "--cache",
20889
+ "--registry"
20890
+ ]);
20891
+ function positional2(words, from, values = PM_VALUES) {
20892
+ let index = from;
20893
+ while (index < words.length && words[index].startsWith("-")) {
20894
+ if (words[index] === "--")
20895
+ return index + 1;
20896
+ index += values.has(words[index]) ? 2 : 1;
20897
+ }
20898
+ return index;
20899
+ }
20900
+ var baseName2 = (word) => (word?.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat|ps1)$/u, "");
20901
+ var WORKIT = /^(?:workit|@brainervirus\/workit-cli(?:@.*)?)$/u;
20902
+ var PYTHONS = /^(?:python[\d.]*|py)$/u;
20903
+ var NPM_INSTALL = new Set([
20904
+ "ci",
20905
+ "clean-install",
20906
+ "cit",
20907
+ "install-test",
20908
+ "it",
20909
+ "install",
20910
+ "i",
20911
+ "in",
20912
+ "add",
20913
+ "rebuild",
20914
+ "update",
20915
+ "up"
20916
+ ]);
20917
+ var NPM_RUN = new Set(["run", "run-script", "rum", "urn"]);
20918
+ var NPM_TEST = new Set(["test", "t", "tst"]);
20919
+ var EXEC = new Set(["exec", "x", "dlx"]);
20920
+ function builtIn(words, depth = 0) {
20921
+ if (depth > 6 || !words.length)
20922
+ return null;
20923
+ const name = baseName2(words[0]);
20924
+ const label = () => words.slice(0, 3).join(" ");
20925
+ const inner = (from) => builtIn(words.slice(from), depth + 1);
20926
+ if (WORKIT.test(name))
20927
+ return null;
20928
+ if (INFO_FLAGS.has(words[1] ?? "") && words.length === 2)
20929
+ return null;
20930
+ if (words.some((word) => word === "--watch" || word.startsWith("--watch=") || word === "--watchAll"))
20931
+ return null;
20932
+ if (name === "npx" || name === "bunx" || name === "pnpx")
20933
+ return inner(positional2(words, 1));
20934
+ if (name === "corepack")
20935
+ return inner(1);
20936
+ if (name === "npm" || name === "pnpm" || name === "yarn" || name === "bun" || name === "deno") {
20937
+ const at = positional2(words, 1);
20938
+ const command = words[at];
20939
+ if (command === undefined)
20940
+ return name === "yarn" ? "yarn (install)" : null;
20941
+ if (name === "deno") {
20942
+ if (["test", "bench", "check", "compile", "lint"].includes(command))
20943
+ return label();
20944
+ if (command === "task" && HEAVY_SCRIPT.test(words[at + 1] ?? ""))
20945
+ return label();
20946
+ return null;
20947
+ }
20948
+ if (NPM_INSTALL.has(command) || NPM_TEST.has(command))
20949
+ return label();
20950
+ if (name === "bun" && command === "build")
20951
+ return label();
20952
+ if (EXEC.has(command))
20953
+ return inner(positional2(words, at + 1));
20954
+ if (name === "yarn" && command === "workspace")
20955
+ return inner(at + 2);
20956
+ if (name === "yarn" && command === "workspaces")
20957
+ return words.slice(at + 1).some((word) => HEAVY_SCRIPT.test(word)) ? label() : null;
20958
+ if (NPM_RUN.has(command)) {
20959
+ const script = words[positional2(words, at + 1)];
20960
+ if (script === undefined || WATCH_SCRIPT.test(script))
20961
+ return null;
20962
+ if (HEAVY_SCRIPT.test(script))
20963
+ return label();
20964
+ return name === "bun" ? inner(positional2(words, at + 1)) : null;
20965
+ }
20966
+ if (name === "npm")
20967
+ return null;
20968
+ if (WATCH_SCRIPT.test(command))
20969
+ return null;
20970
+ if (HEAVY_SCRIPT.test(command) && !/\.[cm]?[jt]sx?$/u.test(command))
20971
+ return label();
20972
+ return inner(at);
20973
+ }
20974
+ if (PYTHONS.test(name)) {
20975
+ const at = words.indexOf("-m");
20976
+ return at > 0 && words[at + 1] ? inner(at + 1) : null;
20977
+ }
20978
+ if (["uv", "poetry", "pdm", "pipenv", "hatch", "rye"].includes(name)) {
20979
+ const at = positional2(words, 1);
20980
+ const command = words[at];
20981
+ if (command === "run")
20982
+ return inner(positional2(words, at + 1));
20983
+ if (command === "pip")
20984
+ return inner(at);
20985
+ return ["sync", "install", "build", "test", "add"].includes(command ?? "") ? label() : null;
20986
+ }
20987
+ if (name === "bundle") {
20988
+ const command = words[positional2(words, 1)];
20989
+ if (command === "exec")
20990
+ return inner(positional2(words, 2));
20991
+ return command === "install" || command === undefined ? label() : null;
20992
+ }
20993
+ if (name === "make" || name === "gmake") {
20994
+ const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
20995
+ if (targets.some((target) => WATCH_SCRIPT.test(target)))
20996
+ return null;
20997
+ return !targets.length || targets.some((target) => HEAVY_SCRIPT.test(target)) ? label() : null;
20998
+ }
20999
+ if (name === "rake") {
21000
+ const targets = words.slice(1).filter((word) => !word.startsWith("-") && !word.includes("="));
21001
+ return targets.some((target) => HEAVY_SCRIPT.test(target) || /^spec(?:$|:)/u.test(target)) ? label() : null;
21002
+ }
21003
+ if (name === "nx") {
21004
+ const at = positional2(words, 1);
21005
+ const command = words[at] ?? "";
21006
+ if (["run-many", "affected"].includes(command))
21007
+ return words.some((word) => /^(?:-t|--targets?)(?:=|$)/u.test(word)) && !words.some((word) => WATCH_SCRIPT.test(word.replace(/^--?targets?=/u, ""))) ? label() : null;
21008
+ if (command === "run") {
21009
+ const target = (words[at + 1] ?? "").split(":")[1] ?? "";
21010
+ return HEAVY_SCRIPT.test(target) && !WATCH_SCRIPT.test(target) ? label() : null;
21011
+ }
21012
+ return HEAVY_SCRIPT.test(command) && !WATCH_SCRIPT.test(command) ? label() : null;
21013
+ }
21014
+ if (name === "docker" || name === "podman" || name === "nerdctl") {
21015
+ const at = positional2(words, 1, new Set(["-H", "--host", "--context", "-c", "--config"]));
21016
+ const command = words[at];
21017
+ if (command === "build")
21018
+ return label();
21019
+ if (command === "buildx" || command === "image" || command === "builder")
21020
+ return words[positional2(words, at + 1)] === "build" ? label() : null;
21021
+ if (command === "compose") {
21022
+ const at2 = positional2(words, at + 1, new Set(["-f", "--file", "-p"]));
21023
+ const action = words[at2];
21024
+ if (action === "build")
21025
+ return label();
21026
+ return action === "up" && words.slice(at2 + 1).includes("--build") ? label() : null;
21027
+ }
21028
+ return null;
21029
+ }
21030
+ const rule = TOOLS[name];
21031
+ if (!rule)
21032
+ return null;
21033
+ if (words.slice(1).some((word) => WATCH_FLAG.test(word) || word === "watch" || word === "dev"))
21034
+ return null;
21035
+ if (rule === "any")
21036
+ return label();
21037
+ const command = words.slice(1).find((word) => !word.startsWith("-") && !word.startsWith("+") || rule.has(word));
21038
+ return command !== undefined && rule.has(command) ? label() : null;
21039
+ }
21040
+ var globWord = (word) => new RegExp(`^${word.replace(/[.+?^${}()|[\]\\]/gu, "\\$&").replaceAll("*", ".*")}$`, "u");
21041
+ function parsePattern(pattern) {
21042
+ const words = pattern.trim().split(/\s+/u).filter(Boolean);
21043
+ return words.length ? words.map(globWord) : null;
21044
+ }
21045
+ var 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]));
21046
+ var readJson = (file) => {
21047
+ try {
21048
+ return JSON.parse(fs18.readFileSync(file, "utf8"));
21049
+ } catch {
21050
+ return;
21051
+ }
21052
+ };
21053
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21054
+ var stringList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
21055
+ function machinePatterns(env) {
21056
+ const value = readJson(heavyCommandsFile(env));
21057
+ return isRecord5(value) ? stringList(value.patterns) : [];
21058
+ }
21059
+ function repoPatterns(cwd) {
21060
+ return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
21061
+ }
21062
+ function heavyLines(lines, options) {
21063
+ const found = [];
21064
+ let patterns = null;
21065
+ for (const line of lines) {
21066
+ const reason = builtIn(line);
21067
+ if (reason) {
21068
+ found.push({ line, source: "catalog", reason });
21069
+ continue;
21070
+ }
21071
+ patterns ??= (options.patterns ?? [
21072
+ ...machinePatterns(options.env ?? process.env).map((pattern) => ({
21073
+ pattern,
21074
+ from: "machine"
21075
+ })),
21076
+ ...repoPatterns(options.cwd).map((pattern) => ({ pattern, from: "repo" }))
21077
+ ]).flatMap((entry) => {
21078
+ const matchers = parsePattern(entry.pattern);
21079
+ return matchers ? [{ ...entry, matchers }] : [];
21080
+ });
21081
+ const hit = patterns.find((entry) => matches(entry.matchers, line));
21082
+ if (hit)
21083
+ found.push({
21084
+ line,
21085
+ source: { pattern: hit.pattern, from: hit.from },
21086
+ reason: `${hit.from} pattern "${hit.pattern}"`
21087
+ });
21088
+ }
21089
+ return found;
21090
+ }
21091
+ var linesOf = (command, dialect) => {
21092
+ try {
21093
+ return commandLines(command, dialect);
21094
+ } catch {
21095
+ return null;
21096
+ }
21097
+ };
21098
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/u.test(word) ? word : `'${word.replaceAll("'", `'\\''`)}'`;
21099
+ var leaseCommand = (words) => `workit check --heavy -- ${words.map(quoteWord).join(" ")}`;
21100
+ function heavyPlan(command, options) {
21101
+ const dialect = options.dialect ?? "posix";
21102
+ if (dialect !== "posix" || !gitTop(options.cwd))
21103
+ return { kind: "none" };
21104
+ const lines = linesOf(command, dialect);
21105
+ if (!lines)
21106
+ return { kind: "none" };
21107
+ const heavy = heavyLines(lines, options);
21108
+ if (!heavy.length)
21109
+ return { kind: "none" };
21110
+ const simple = simpleCommand(command, dialect);
21111
+ const single = simple && !WORKIT.test(baseName2(simple[0])) && heavyLines([simple], options)[0];
21112
+ const judged = single ? [{ words: lines.flat(), text: command }] : heavy.map((match) => ({
21113
+ words: match.line,
21114
+ text: match.line.join(" ")
21115
+ }));
21116
+ if (options.rules !== null)
21117
+ for (const { words, text } of judged) {
21118
+ const verdict = wrapAllowed(options.rules, words, text, { bypass: options.bypass });
21119
+ if (!verdict.ok)
21120
+ return {
21121
+ kind: "passthrough",
21122
+ 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.`
21123
+ };
21124
+ }
21125
+ if (single) {
21126
+ const rewrite = leaseRewrite(command);
21127
+ return rewrite ? { kind: "rewrite", command: rewrite, reason: single.reason } : { kind: "none" };
21128
+ }
21129
+ const parts = heavy.map((match) => leaseCommand(match.line));
21130
+ return {
21131
+ kind: "split",
21132
+ 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.`,
21133
+ unblock: parts[0] ?? null
21134
+ };
21135
+ }
21136
+ function workitOnPath2(env = process.env) {
21137
+ const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
21138
+ for (const dir of (env.PATH ?? "").split(path23.delimiter))
21139
+ for (const name of names) {
21140
+ if (!dir)
21141
+ continue;
21142
+ try {
21143
+ fs18.accessSync(path23.join(dir, name), fs18.constants.X_OK);
21144
+ return true;
21145
+ } catch {}
21146
+ }
21147
+ return false;
21148
+ }
21149
+ var ASSIGNMENTS = /^\s*((?:[A-Za-z_]\w*=(?:'[^']*'|"(?:[^"\\]|\\.)*"|[^\s'"])*\s+)*)/u;
21150
+ function leaseRewrite(command) {
21151
+ const text = command.trim();
21152
+ const prefix = ASSIGNMENTS.exec(text)?.[1] ?? "";
21153
+ const rest = text.slice(prefix.length).trim();
21154
+ if (!rest)
21155
+ return null;
21156
+ return `${prefix}workit check --heavy -- ${rest}`;
21157
+ }
21158
+
21159
+ // packages/workit-core/src/core/heavy-words.ts
21160
+ import fs19 from "node:fs";
21161
+ import os8 from "node:os";
21162
+ import path24 from "node:path";
21163
+ var 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(" "));
21164
+ var wordsOf = (command) => command.split(/[\s;&|()<>`'"]+/u).map((word) => (word.split(/[\\/]/u).at(-1) ?? "").toLowerCase().replace(/\.(?:exe|cmd|bat)$/u, "")).filter(Boolean);
21165
+ var stateDir2 = (env) => {
21166
+ if (env.WORKFLOW_TOOLKIT_STATE)
21167
+ return env.WORKFLOW_TOOLKIT_STATE;
21168
+ if (env.XDG_STATE_HOME)
21169
+ return path24.join(env.XDG_STATE_HOME, "workit");
21170
+ const home = env.HOME || os8.homedir();
21171
+ if (process.platform === "darwin")
21172
+ return path24.join(home, "Library", "Application Support", "workit");
21173
+ if (process.platform === "win32")
21174
+ return path24.join(env.LOCALAPPDATA ?? path24.join(home, "AppData", "Local"), "workit");
21175
+ return path24.join(home, ".local", "state", "workit");
21176
+ };
21177
+ var readJson2 = (file) => {
21178
+ try {
21179
+ return JSON.parse(fs19.readFileSync(file, "utf8"));
21180
+ } catch {
21181
+ return;
21182
+ }
21183
+ };
21184
+ var patternWords = (cwd, env) => {
21185
+ const patterns = [];
21186
+ const machine = readJson2(path24.join(stateDir2(env), "heavy-commands.json"));
21187
+ if (Array.isArray(machine?.patterns))
21188
+ patterns.push(...machine.patterns);
21189
+ for (let dir = path24.resolve(cwd);; dir = path24.dirname(dir)) {
21190
+ if (fs19.existsSync(path24.join(dir, ".git"))) {
21191
+ patterns.push(...repoHeavyCommands(dir));
21192
+ break;
21193
+ }
21194
+ if (path24.dirname(dir) === dir)
21195
+ break;
21196
+ }
21197
+ const words = new Set;
21198
+ for (const pattern of patterns) {
21199
+ if (typeof pattern !== "string")
21200
+ continue;
21201
+ const first = wordsOf(pattern)[0];
21202
+ if (!first || first.includes("*"))
21203
+ return null;
21204
+ words.add(first);
21205
+ }
21206
+ return words;
21207
+ };
21208
+ function maybeHeavy(command, cwd, env = process.env) {
21209
+ try {
21210
+ const words = wordsOf(command);
21211
+ if (words.some((word) => HEAVY_WORDS.has(word) || /^python[\d.]*$/u.test(word)))
21212
+ return true;
21213
+ const extra = patternWords(cwd, env);
21214
+ return extra === null || words.some((word) => extra.has(word));
21215
+ } catch {
21216
+ return true;
21217
+ }
21218
+ }
21219
+
21220
+ // packages/workit-core/src/hooks/heavy-gate.ts
21221
+ var rulesFor = (host, cwd, env) => {
21222
+ switch (host) {
21223
+ case "claude_code":
21224
+ return claudeRules(cwd, env);
21225
+ case "opencode":
21226
+ return opencodeRules(cwd, env);
21227
+ case "codex_cli":
21228
+ case "codex_desktop":
21229
+ return codexRules(cwd, env);
21230
+ case "cursor":
21231
+ return cursorRules(cwd, env);
21232
+ case "pi":
21233
+ return null;
21234
+ }
21235
+ };
21236
+ function heavyGate(cwd, command, options) {
21237
+ const env = options.env ?? process.env;
21238
+ try {
21239
+ if (env.WORKIT_HEAVY_GATE === "0" || !maybeHeavy(command, cwd, env) || !workitOnPath2(env))
21240
+ return { kind: "none" };
21241
+ return heavyPlan(command, {
21242
+ cwd,
21243
+ env,
21244
+ dialect: options.dialect ?? "posix",
21245
+ rules: rulesFor(options.host, cwd, env),
21246
+ bypass: options.permissionMode === "bypassPermissions"
21247
+ });
21248
+ } catch {
21249
+ return { kind: "none" };
21250
+ }
21251
+ }
21252
+ var 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.`;
21253
+ var heavyDenial = (original, plan) => ({
21254
+ kind: "deny",
21255
+ 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}`,
21256
+ unblock: plan.command
21257
+ });
21258
+ var splitDenial = (plan) => ({
21259
+ kind: "deny",
21260
+ reason: plan.reason,
21261
+ unblock: plan.unblock
21262
+ });
21263
+ // packages/workit-core/src/hooks/write-gate.ts
21264
+ import { realpathSync as realpathSync5 } from "node:fs";
21265
+ import os10 from "node:os";
21266
+ import path26 from "node:path";
20327
21267
 
20328
21268
  // packages/workit-core/src/core/live-session.ts
20329
21269
  import { spawnSync as spawnSync13 } from "node:child_process";
20330
21270
  import { createHash as createHash15 } from "node:crypto";
20331
- import fs17 from "node:fs";
20332
- import os6 from "node:os";
20333
- import path22 from "node:path";
21271
+ import fs20 from "node:fs";
21272
+ import os9 from "node:os";
21273
+ import path25 from "node:path";
20334
21274
  var LIVE_SESSION_TTL_MS = 15 * 60000;
20335
21275
  var HEARTBEAT_EVERY_MS = 30000;
20336
21276
  var liveProbe = {
@@ -20351,9 +21291,9 @@ var processAlive = (pid) => {
20351
21291
  var processInfo = (pid) => {
20352
21292
  try {
20353
21293
  if (liveProbe.platform === "linux") {
20354
- const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
21294
+ const stat = fs20.readFileSync(`/proc/${pid}/stat`, "utf8");
20355
21295
  const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20356
- const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
21296
+ const command = fs20.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20357
21297
  return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20358
21298
  }
20359
21299
  if (liveProbe.platform === "win32")
@@ -20404,35 +21344,35 @@ var liveOwner = (input, transport, env = process.env) => {
20404
21344
  };
20405
21345
  };
20406
21346
  var checkoutOf = (target) => {
20407
- let dir = path22.resolve(target);
20408
- while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20409
- dir = path22.dirname(dir);
20410
- if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20411
- dir = path22.dirname(dir);
20412
- const parts = dir.split(path22.sep);
21347
+ let dir = path25.resolve(target);
21348
+ while (!fs20.existsSync(dir) && path25.dirname(dir) !== dir)
21349
+ dir = path25.dirname(dir);
21350
+ if (fs20.existsSync(dir) && !fs20.statSync(dir).isDirectory())
21351
+ dir = path25.dirname(dir);
21352
+ const parts = dir.split(path25.sep);
20413
21353
  const dotGit = parts.lastIndexOf(".git");
20414
21354
  if (dotGit > 0)
20415
- dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
21355
+ dir = parts.slice(0, dotGit).join(path25.sep) || path25.sep;
20416
21356
  const location = resolveStore(dir);
20417
21357
  if (location instanceof Error || !location.shared || !location.top)
20418
21358
  return null;
20419
21359
  let top = location.top;
20420
21360
  try {
20421
- top = fs17.realpathSync.native(top);
21361
+ top = fs20.realpathSync.native(top);
20422
21362
  } catch {}
20423
21363
  return top;
20424
21364
  };
20425
- var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
21365
+ var markerFile2 = (top) => path25.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20426
21366
  var checkoutKey = (top, platform = process.platform) => {
20427
- let key = path22.resolve(top);
21367
+ let key = path25.resolve(top);
20428
21368
  try {
20429
- key = fs17.realpathSync.native(key);
21369
+ key = fs20.realpathSync.native(key);
20430
21370
  } catch {}
20431
21371
  return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20432
21372
  };
20433
21373
  var readMarker = (file) => {
20434
21374
  try {
20435
- const value = JSON.parse(fs17.readFileSync(file, "utf8"));
21375
+ const value = JSON.parse(fs20.readFileSync(file, "utf8"));
20436
21376
  if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20437
21377
  return null;
20438
21378
  return {
@@ -20455,7 +21395,7 @@ var isLive = (marker, deps) => {
20455
21395
  const age = ageOf2(marker, deps.now ?? new Date);
20456
21396
  if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20457
21397
  return false;
20458
- if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
21398
+ if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
20459
21399
  return true;
20460
21400
  if (!(deps.alive ?? processAlive)(marker.pid))
20461
21401
  return false;
@@ -20484,7 +21424,7 @@ var liveConflict = (target, owner, deps = {}) => {
20484
21424
  const marker = readMarker(file);
20485
21425
  if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20486
21426
  return null;
20487
- if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
21427
+ if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
20488
21428
  return null;
20489
21429
  return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20490
21430
  } catch {
@@ -20498,7 +21438,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
20498
21438
  return;
20499
21439
  const file = markerFile2(checkout);
20500
21440
  const now = deps.now ?? new Date;
20501
- const machine = deps.machine ?? os6.hostname();
21441
+ const machine = deps.machine ?? os9.hostname();
20502
21442
  const current = readMarker(file);
20503
21443
  if (current) {
20504
21444
  const age = ageOf2(current, now);
@@ -20519,10 +21459,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
20519
21459
  checkout,
20520
21460
  heartbeatAt: now.toISOString()
20521
21461
  };
20522
- fs17.mkdirSync(path22.dirname(file), { recursive: true });
21462
+ fs20.mkdirSync(path25.dirname(file), { recursive: true });
20523
21463
  const temp = `${file}.${process.pid}.tmp`;
20524
- fs17.writeFileSync(temp, JSON.stringify(marker));
20525
- fs17.renameSync(temp, file);
21464
+ fs20.writeFileSync(temp, JSON.stringify(marker));
21465
+ fs20.renameSync(temp, file);
20526
21466
  } catch {}
20527
21467
  };
20528
21468
 
@@ -20531,27 +21471,42 @@ var NONE3 = { kind: "none" };
20531
21471
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20532
21472
  var canonical2 = (file) => {
20533
21473
  let rest = "";
20534
- for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
21474
+ for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
20535
21475
  try {
20536
- return path23.join(realpathSync5(dir), rest);
21476
+ return path26.join(realpathSync5(dir), rest);
20537
21477
  } catch {
20538
- if (path23.dirname(dir) === dir)
20539
- return path23.resolve(file);
20540
- rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
21478
+ if (path26.dirname(dir) === dir)
21479
+ return path26.resolve(file);
21480
+ rest = rest ? path26.join(path26.basename(dir), rest) : path26.basename(dir);
20541
21481
  }
20542
21482
  }
20543
21483
  };
20544
21484
  var exempt = (cwd, root, file, plans) => {
20545
21485
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20546
- const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20547
- if (relative.startsWith("..") || path23.isAbsolute(relative))
21486
+ const relative = path26.relative(fold(canonical2(root)), fold(canonical2(path26.resolve(cwd, file))));
21487
+ if (relative.startsWith("..") || path26.isAbsolute(relative))
20548
21488
  return true;
20549
- const posix = relative.split(path23.sep).join("/");
21489
+ const posix = relative.split(path26.sep).join("/");
20550
21490
  const segments = posix.split("/");
20551
21491
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20552
21492
  };
20553
21493
  var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20554
21494
  var operands = (words) => words.filter((item) => !item.startsWith("-"));
21495
+ var SCRIPT_SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
21496
+ var wrappedScript = (input) => {
21497
+ let words = input;
21498
+ while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
21499
+ words = words.slice(1);
21500
+ const tail = workitCheckTail(words);
21501
+ if (!tail)
21502
+ return null;
21503
+ if ("script" in tail)
21504
+ return tail.script;
21505
+ const [shell, flag, script] = tail.argv;
21506
+ if (SCRIPT_SHELLS.has((shell.split(/[\\/]/).at(-1) ?? "").toLowerCase()) && /^-[a-z]*c$/.test(flag ?? ""))
21507
+ return script ?? null;
21508
+ return tail.argv.map(quoteWord2).join(" ");
21509
+ };
20555
21510
  var editsOf = (input) => {
20556
21511
  let words = input;
20557
21512
  while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
@@ -20628,10 +21583,18 @@ var editsOf = (input) => {
20628
21583
  return [];
20629
21584
  }
20630
21585
  };
20631
- var shellWrites = (command) => {
21586
+ var shellWrites = (command, depth = 0) => {
20632
21587
  const targets = [];
20633
21588
  let unknown = false;
20634
21589
  for (const segment of segmentsOf(command)) {
21590
+ const inner = depth < 4 ? wrappedScript(segment.words) : null;
21591
+ if (inner !== null) {
21592
+ const nested = shellWrites(inner, depth + 1);
21593
+ if (nested.writes && nested.targets === null)
21594
+ unknown = true;
21595
+ else if (nested.targets)
21596
+ targets.push(...nested.targets);
21597
+ }
20635
21598
  targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
20636
21599
  const edits = editsOf(segment.words);
20637
21600
  if (edits === null)
@@ -20644,7 +21607,7 @@ var shellWrites = (command) => {
20644
21607
  return { writes: true, targets: unknown ? null : targets };
20645
21608
  };
20646
21609
  var cache2 = new Map;
20647
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
21610
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path26.resolve(root, file)) ?? "-").join("\x00");
20648
21611
  var gateFor = (cwd) => {
20649
21612
  const store = new TaskStore(cwd);
20650
21613
  const listed = store.listTaskIndex();
@@ -20723,7 +21686,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
20723
21686
  return [];
20724
21687
  if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20725
21688
  return [];
20726
- return [path23.resolve(cwd, invocation.dir ?? ".")];
21689
+ return [path26.resolve(cwd, invocation.dir ?? ".")];
20727
21690
  });
20728
21691
  };
20729
21692
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
@@ -20733,15 +21696,15 @@ var cdInto = (dir, words) => {
20733
21696
  const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20734
21697
  if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20735
21698
  return;
20736
- return target === undefined ? os7.homedir() : path23.resolve(dir, target);
21699
+ return target === undefined ? os10.homedir() : path26.resolve(dir, target);
20737
21700
  };
20738
- var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20739
- var segmentTargets = (cwd, command, dialect) => {
21701
+ var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
21702
+ var segmentTargets = (cwd, command, dialect, depth = 0) => {
20740
21703
  const targets = [];
20741
21704
  let dir = cwd;
20742
21705
  let stack = [];
20743
21706
  const groups = [];
20744
- const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
21707
+ const at = (file) => path26.isAbsolute(file) ? file : dir === undefined ? null : path26.resolve(dir, file);
20745
21708
  for (const segment of segmentsOf(command, dialect)) {
20746
21709
  if (segment.group === "open") {
20747
21710
  groups.push({ dir, stack: [...stack] });
@@ -20769,6 +21732,9 @@ var segmentTargets = (cwd, command, dialect) => {
20769
21732
  dir = stack.length ? stack.pop() : undefined;
20770
21733
  continue;
20771
21734
  }
21735
+ const inner = depth < 4 ? wrappedScript(words) : null;
21736
+ if (inner !== null && dir !== undefined)
21737
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20772
21738
  for (const file of segment.redirects)
20773
21739
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20774
21740
  const resolved = at(file);
@@ -20778,15 +21744,15 @@ var segmentTargets = (cwd, command, dialect) => {
20778
21744
  const edits = editsOf(segment.words);
20779
21745
  if (edits === null) {
20780
21746
  if (dir !== undefined)
20781
- targets.push(path23.join(dir, DIR_TARGET));
21747
+ targets.push(path26.join(dir, DIR_TARGET));
20782
21748
  } else
20783
21749
  for (const file of edits) {
20784
21750
  const resolved = at(file);
20785
21751
  if (resolved)
20786
21752
  targets.push(resolved);
20787
21753
  }
20788
- 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), "")))
20789
- targets.push(path23.join(dir, DIR_TARGET));
21754
+ 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), "")))
21755
+ targets.push(path26.join(dir, DIR_TARGET));
20790
21756
  }
20791
21757
  return targets;
20792
21758
  };
@@ -20821,7 +21787,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20821
21787
  var guarded = (targets) => {
20822
21788
  const seen = new Set;
20823
21789
  return targets.filter((target) => {
20824
- const dir = path23.dirname(target);
21790
+ const dir = path26.dirname(target);
20825
21791
  if (seen.has(dir))
20826
21792
  return false;
20827
21793
  seen.add(dir);
@@ -20842,7 +21808,7 @@ var claimAll = (owner, targets, deps) => {
20842
21808
  for (const target of guarded(targets))
20843
21809
  claimLiveSession(target, owner, deps);
20844
21810
  };
20845
- var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
21811
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path26.resolve(cwd, file)) : [path26.join(cwd, DIR_TARGET)];
20846
21812
  function writeGate(cwd, paths, owner, deps) {
20847
21813
  if (!owner)
20848
21814
  return beforeWriteGate(cwd, paths);
@@ -20862,7 +21828,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20862
21828
  try {
20863
21829
  targets = [
20864
21830
  ...segmentTargets(cwd, command, dialect),
20865
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
21831
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path26.join(dir, DIR_TARGET))
20866
21832
  ];
20867
21833
  } catch {
20868
21834
  targets = [];
@@ -21296,12 +22262,12 @@ ${line}` };
21296
22262
  };
21297
22263
  // packages/workit-core/src/hooks/hosts/fields.ts
21298
22264
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
21299
- import path24 from "node:path";
21300
- var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
22265
+ import path27 from "node:path";
22266
+ var isRecord6 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
21301
22267
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
21302
22268
  var optionalText = (value) => typeof value === "string" ? value : null;
21303
22269
  var existingDirectory = (value) => {
21304
- if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
22270
+ if (!nonEmpty2(value) || !path27.isAbsolute(value) || !existsSync7(value))
21305
22271
  return null;
21306
22272
  try {
21307
22273
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -21354,7 +22320,7 @@ var isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toL
21354
22320
  var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
21355
22321
  var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
21356
22322
  var writePaths = (input) => {
21357
- if (!isRecord4(input))
22323
+ if (!isRecord6(input))
21358
22324
  return [];
21359
22325
  const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
21360
22326
  for (const value of Object.values(input))
@@ -21449,10 +22415,32 @@ function handleHook(input, deps) {
21449
22415
  const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
21450
22416
  if (gate.kind === "deny")
21451
22417
  return gate;
22418
+ const plan = heavyGate(input.cwd, event.command, {
22419
+ host: input.host,
22420
+ dialect: event.dialect,
22421
+ permissionMode: input.permissionMode
22422
+ });
22423
+ if (plan.kind === "split" && canDeny)
22424
+ return splitDenial(plan);
22425
+ const rewrites = usable2(descriptor.shellPolicy.rewrite);
22426
+ if (plan.kind === "rewrite" && !rewrites && canDeny)
22427
+ return heavyDenial(event.command, plan);
22428
+ const lease = plan.kind === "rewrite" && rewrites ? plan : null;
22429
+ const aside = plan.kind === "passthrough" ? plan.note : null;
21452
22430
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
22431
+ const rewritten = (text) => lease ? {
22432
+ kind: "rewrite",
22433
+ command: lease.command,
22434
+ input: event.toolInput ? { ...event.toolInput, command: lease.command } : null,
22435
+ text
22436
+ } : NONE5;
21453
22437
  if (descriptor.transport !== "hook-process")
21454
- return NONE5;
21455
- return withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, shipNudge(input, event.command));
22438
+ return rewritten(null);
22439
+ const nudged = withContextLine(withContextLine(canDeny ? rawGitPre(input, event.command) : NONE5, aside), shipNudge(input, event.command));
22440
+ if (!lease)
22441
+ return nudged;
22442
+ return rewritten([rewriteNote(lease), nudged.kind === "context" ? nudged.text : null].filter(Boolean).join(`
22443
+ `));
21456
22444
  }
21457
22445
  case "shell.post":
21458
22446
  if (usable2(descriptor.events["shell.post"].support))
@@ -21530,7 +22518,12 @@ var CLAUDE_CODE_DESCRIPTOR = {
21530
22518
  "compact.pre": { support: "none", native: null },
21531
22519
  stop: { support: "native", native: "Stop" }
21532
22520
  },
21533
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22521
+ shellPolicy: {
22522
+ deny: "native",
22523
+ channel: "permissionDecision",
22524
+ failClosed: false,
22525
+ rewrite: "native"
22526
+ },
21534
22527
  context: {
21535
22528
  sessionStart: "native",
21536
22529
  perTurn: "native",
@@ -21612,7 +22605,7 @@ var EVENTS = {
21612
22605
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
21613
22606
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
21614
22607
  var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
21615
- var toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
22608
+ var toolCommand = (value) => isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
21616
22609
  var eventOf = (name, value) => {
21617
22610
  const toolUseId = optionalText(value.tool_use_id);
21618
22611
  switch (name) {
@@ -21640,19 +22633,28 @@ var eventOf = (name, value) => {
21640
22633
  kind: "tool.pre",
21641
22634
  tool: value.tool_name,
21642
22635
  toolUseId,
21643
- skill: isRecord4(value.tool_input) ? optionalText(value.tool_input.skill) : null
22636
+ skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
21644
22637
  }
21645
22638
  };
21646
22639
  if (!SHELL_TOOLS.has(value.tool_name))
21647
22640
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
21648
22641
  const command = toolCommand(value);
21649
- 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}` };
22642
+ return command ? {
22643
+ ok: true,
22644
+ event: {
22645
+ kind: "shell.pre",
22646
+ command,
22647
+ toolUseId,
22648
+ dialect: dialectOf2(value),
22649
+ toolInput: value.tool_input
22650
+ }
22651
+ } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
21650
22652
  }
21651
22653
  case "PostToolUse": {
21652
22654
  const command = toolCommand(value);
21653
22655
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
21654
22656
  return { ok: false, error: "only shell PostToolUse is mapped" };
21655
- const response = isRecord4(value.tool_response) ? value.tool_response : {};
22657
+ const response = isRecord6(value.tool_response) ? value.tool_response : {};
21656
22658
  return {
21657
22659
  ok: true,
21658
22660
  event: {
@@ -21710,6 +22712,17 @@ var CONTEXT_EVENTS = new Set([
21710
22712
  "PostToolUse"
21711
22713
  ]);
21712
22714
  var render = (decision, native) => {
22715
+ if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
22716
+ return {
22717
+ json: {
22718
+ hookSpecificOutput: {
22719
+ hookEventName: "PreToolUse",
22720
+ updatedInput: decision.input,
22721
+ ...decision.text ? { additionalContext: decision.text } : {}
22722
+ }
22723
+ },
22724
+ exitCode: 0
22725
+ };
21713
22726
  if (decision.kind === "deny" && native === "PreToolUse")
21714
22727
  return {
21715
22728
  json: {
@@ -21735,14 +22748,14 @@ var render = (decision, native) => {
21735
22748
  var claudeCodeAdapter = {
21736
22749
  descriptor: CLAUDE_CODE_DESCRIPTOR,
21737
22750
  parse(raw) {
21738
- const native = isRecord4(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22751
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
21739
22752
  const fail = (error) => ({
21740
22753
  ok: false,
21741
22754
  error,
21742
22755
  native,
21743
22756
  event: native ? EVENTS[native] : null
21744
22757
  });
21745
- if (!isRecord4(raw) || !native)
22758
+ if (!isRecord6(raw) || !native)
21746
22759
  return fail("hook_event_name is required");
21747
22760
  if (!nonEmpty2(raw.session_id))
21748
22761
  return fail("session_id is required");
@@ -21796,7 +22809,12 @@ var CODEX_DESCRIPTOR = {
21796
22809
  "compact.pre": { support: "none", native: null },
21797
22810
  stop: { support: "native", native: "Stop" }
21798
22811
  },
21799
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22812
+ shellPolicy: {
22813
+ deny: "native",
22814
+ channel: "permissionDecision",
22815
+ failClosed: false,
22816
+ rewrite: "none"
22817
+ },
21800
22818
  context: {
21801
22819
  sessionStart: "native",
21802
22820
  perTurn: "native",
@@ -21888,7 +22906,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
21888
22906
  var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
21889
22907
  var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
21890
22908
  var parseCodexHookInput = (value) => {
21891
- if (!isRecord4(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
22909
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
21892
22910
  return { ok: false, error: "hook_event_name is required" };
21893
22911
  const event = value.hook_event_name;
21894
22912
  const cwd = existingDirectory(value.cwd);
@@ -21897,7 +22915,7 @@ var parseCodexHookInput = (value) => {
21897
22915
  const toolEvent = event === "PreToolUse" || event === "PostToolUse";
21898
22916
  if (toolEvent && !nonEmpty2(value.tool_name))
21899
22917
  return { ok: false, error: "tool_name is required" };
21900
- const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
22918
+ const command = isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
21901
22919
  if (toolEvent && isShellTool(value.tool_name) && !command)
21902
22920
  return { ok: false, error: "tool_input.command is required for shell tools" };
21903
22921
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -22011,7 +23029,7 @@ var codexAdapter = {
22011
23029
  const host = detectCodexSurface(env);
22012
23030
  const parsed = parseCodexHookInput(raw);
22013
23031
  if (!parsed.ok) {
22014
- const native = isRecord4(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
23032
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22015
23033
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
22016
23034
  }
22017
23035
  const input = parsed.data;
@@ -22038,8 +23056,8 @@ var codexAdapter = {
22038
23056
  };
22039
23057
 
22040
23058
  // packages/workit-core/src/hooks/hosts/cursor.ts
22041
- import fs18 from "node:fs";
22042
- import path25 from "node:path";
23059
+ import fs21 from "node:fs";
23060
+ import path28 from "node:path";
22043
23061
  var undocumented = { support: "undocumented", native: null };
22044
23062
  var none = { support: "none", native: null };
22045
23063
  var CURSOR_DESCRIPTOR = {
@@ -22061,7 +23079,7 @@ var CURSOR_DESCRIPTOR = {
22061
23079
  "compact.pre": { support: "partial", native: "preCompact" },
22062
23080
  stop: { support: "native", native: "stop" }
22063
23081
  },
22064
- shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
23082
+ shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
22065
23083
  context: {
22066
23084
  sessionStart: "native",
22067
23085
  perTurn: "none",
@@ -22154,7 +23172,7 @@ var EVENTS3 = {
22154
23172
  stop: "stop"
22155
23173
  };
22156
23174
  var parseCursorHookInput = (value) => {
22157
- if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
23175
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
22158
23176
  return { ok: false, error: "hook_event_name is required" };
22159
23177
  const roots = value.workspace_roots;
22160
23178
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -22236,17 +23254,17 @@ var protocolEvent2 = (input) => {
22236
23254
  };
22237
23255
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
22238
23256
  var lastAssistantText = (file) => {
22239
- if (!file || !path25.isAbsolute(file))
23257
+ if (!file || !path28.isAbsolute(file))
22240
23258
  return null;
22241
23259
  try {
22242
- const size = fs18.statSync(file).size;
23260
+ const size = fs21.statSync(file).size;
22243
23261
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
22244
23262
  const buffer = Buffer.alloc(size - start);
22245
- const fd = fs18.openSync(file, "r");
23263
+ const fd = fs21.openSync(file, "r");
22246
23264
  try {
22247
- fs18.readSync(fd, buffer, 0, buffer.length, start);
23265
+ fs21.readSync(fd, buffer, 0, buffer.length, start);
22248
23266
  } finally {
22249
- fs18.closeSync(fd);
23267
+ fs21.closeSync(fd);
22250
23268
  }
22251
23269
  const lines = buffer.toString("utf8").split(`
22252
23270
  `);
@@ -22257,14 +23275,14 @@ var lastAssistantText = (file) => {
22257
23275
  } catch {
22258
23276
  continue;
22259
23277
  }
22260
- if (!isRecord4(entry) || entry.role !== "assistant")
23278
+ if (!isRecord6(entry) || entry.role !== "assistant")
22261
23279
  continue;
22262
- const content = isRecord4(entry.message) ? entry.message.content : entry.content;
23280
+ const content = isRecord6(entry.message) ? entry.message.content : entry.content;
22263
23281
  if (typeof content === "string")
22264
23282
  return content;
22265
23283
  if (!Array.isArray(content))
22266
23284
  return null;
22267
- const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
23285
+ const text = content.filter((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
22268
23286
  `);
22269
23287
  return text || null;
22270
23288
  }
@@ -22273,7 +23291,7 @@ var lastAssistantText = (file) => {
22273
23291
  return null;
22274
23292
  }
22275
23293
  };
22276
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
23294
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path28.sep) ? root : `${root}${path28.sep}`);
22277
23295
  var hookCwd = (input) => {
22278
23296
  const cwd = existingDirectory(input.cwd);
22279
23297
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -22314,7 +23332,7 @@ var cursorAdapter = {
22314
23332
  parse(raw) {
22315
23333
  const parsed = parseCursorHookInput(raw);
22316
23334
  if (!parsed.ok) {
22317
- const native = isRecord4(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
23335
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22318
23336
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
22319
23337
  }
22320
23338
  const input = parsed.data;