@brainervirus/workit-cursor 8.16.0 → 8.17.1

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,
@@ -19232,7 +19244,16 @@ function rawInvocations(command, dialect = "posix", depth = 0) {
19232
19244
  return found;
19233
19245
  }
19234
19246
  var RECENT = new Map;
19235
- 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) {
19236
19257
  const found = [];
19237
19258
  let dir = null;
19238
19259
  const groups = [];
@@ -19243,7 +19264,8 @@ function walk2(command, dialect, depth) {
19243
19264
  const nested = (script, inner) => {
19244
19265
  if (!script || depth >= MAX_DEPTH)
19245
19266
  return;
19246
- 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)
19247
19269
  found.push({ ...invocation, dir: rebase(dir, invocation.dir) });
19248
19270
  };
19249
19271
  const visit = (input, stdin, hops) => {
@@ -19255,11 +19277,17 @@ function walk2(command, dialect, depth) {
19255
19277
  const name = commandName(words[0]);
19256
19278
  if (!name)
19257
19279
  return;
19280
+ seen?.push(words);
19281
+ if (name === "-c" || name === "--command")
19282
+ return nested(words[1], "posix");
19258
19283
  const runs = (lines) => {
19259
19284
  if (hops < MAX_DEPTH)
19260
19285
  for (const next of lines)
19261
19286
  visit(next, undefined, hops + 1);
19262
19287
  };
19288
+ const wrapped = seen ? null : workitCheckTail(words);
19289
+ if (wrapped)
19290
+ return "script" in wrapped ? nested(wrapped.script, "posix") : runs([wrapped.argv]);
19263
19291
  if (CD.has(name)) {
19264
19292
  const target = cdTarget(words);
19265
19293
  dir = target === null ? os5.homedir() : joinDir(dir, target);
@@ -19350,6 +19378,51 @@ var LAUNCHERS = new Set([
19350
19378
  "command"
19351
19379
  ]);
19352
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
+ }
19353
19426
  function workitInvocations(command, dialect = "posix", depth = 0) {
19354
19427
  const found = [];
19355
19428
  for (const { words } of segmentsOf(command, dialect)) {
@@ -20377,17 +20450,827 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20377
20450
  }
20378
20451
  return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
20379
20452
  };
20380
- // packages/workit-core/src/hooks/write-gate.ts
20381
- 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";
20382
20770
  import os7 from "node:os";
20383
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";
20384
21267
 
20385
21268
  // packages/workit-core/src/core/live-session.ts
20386
21269
  import { spawnSync as spawnSync13 } from "node:child_process";
20387
21270
  import { createHash as createHash15 } from "node:crypto";
20388
- import fs17 from "node:fs";
20389
- import os6 from "node:os";
20390
- import path22 from "node:path";
21271
+ import fs20 from "node:fs";
21272
+ import os9 from "node:os";
21273
+ import path25 from "node:path";
20391
21274
  var LIVE_SESSION_TTL_MS = 15 * 60000;
20392
21275
  var HEARTBEAT_EVERY_MS = 30000;
20393
21276
  var liveProbe = {
@@ -20408,9 +21291,9 @@ var processAlive = (pid) => {
20408
21291
  var processInfo = (pid) => {
20409
21292
  try {
20410
21293
  if (liveProbe.platform === "linux") {
20411
- const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
21294
+ const stat = fs20.readFileSync(`/proc/${pid}/stat`, "utf8");
20412
21295
  const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20413
- const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
21296
+ const command = fs20.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20414
21297
  return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20415
21298
  }
20416
21299
  if (liveProbe.platform === "win32")
@@ -20461,35 +21344,35 @@ var liveOwner = (input, transport, env = process.env) => {
20461
21344
  };
20462
21345
  };
20463
21346
  var checkoutOf = (target) => {
20464
- let dir = path22.resolve(target);
20465
- while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20466
- dir = path22.dirname(dir);
20467
- if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20468
- dir = path22.dirname(dir);
20469
- 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);
20470
21353
  const dotGit = parts.lastIndexOf(".git");
20471
21354
  if (dotGit > 0)
20472
- dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
21355
+ dir = parts.slice(0, dotGit).join(path25.sep) || path25.sep;
20473
21356
  const location = resolveStore(dir);
20474
21357
  if (location instanceof Error || !location.shared || !location.top)
20475
21358
  return null;
20476
21359
  let top = location.top;
20477
21360
  try {
20478
- top = fs17.realpathSync.native(top);
21361
+ top = fs20.realpathSync.native(top);
20479
21362
  } catch {}
20480
21363
  return top;
20481
21364
  };
20482
- 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`);
20483
21366
  var checkoutKey = (top, platform = process.platform) => {
20484
- let key = path22.resolve(top);
21367
+ let key = path25.resolve(top);
20485
21368
  try {
20486
- key = fs17.realpathSync.native(key);
21369
+ key = fs20.realpathSync.native(key);
20487
21370
  } catch {}
20488
21371
  return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20489
21372
  };
20490
21373
  var readMarker = (file) => {
20491
21374
  try {
20492
- const value = JSON.parse(fs17.readFileSync(file, "utf8"));
21375
+ const value = JSON.parse(fs20.readFileSync(file, "utf8"));
20493
21376
  if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20494
21377
  return null;
20495
21378
  return {
@@ -20512,7 +21395,7 @@ var isLive = (marker, deps) => {
20512
21395
  const age = ageOf2(marker, deps.now ?? new Date);
20513
21396
  if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20514
21397
  return false;
20515
- if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
21398
+ if (marker.machine !== (deps.machine ?? os9.hostname()) || marker.pid === null)
20516
21399
  return true;
20517
21400
  if (!(deps.alive ?? processAlive)(marker.pid))
20518
21401
  return false;
@@ -20541,7 +21424,7 @@ var liveConflict = (target, owner, deps = {}) => {
20541
21424
  const marker = readMarker(file);
20542
21425
  if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20543
21426
  return null;
20544
- if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
21427
+ if (sameOwner(marker, owner, deps.machine ?? os9.hostname()))
20545
21428
  return null;
20546
21429
  return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20547
21430
  } catch {
@@ -20555,7 +21438,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
20555
21438
  return;
20556
21439
  const file = markerFile2(checkout);
20557
21440
  const now = deps.now ?? new Date;
20558
- const machine = deps.machine ?? os6.hostname();
21441
+ const machine = deps.machine ?? os9.hostname();
20559
21442
  const current = readMarker(file);
20560
21443
  if (current) {
20561
21444
  const age = ageOf2(current, now);
@@ -20576,10 +21459,10 @@ var claimLiveSession = (target, owner, deps = {}) => {
20576
21459
  checkout,
20577
21460
  heartbeatAt: now.toISOString()
20578
21461
  };
20579
- fs17.mkdirSync(path22.dirname(file), { recursive: true });
21462
+ fs20.mkdirSync(path25.dirname(file), { recursive: true });
20580
21463
  const temp = `${file}.${process.pid}.tmp`;
20581
- fs17.writeFileSync(temp, JSON.stringify(marker));
20582
- fs17.renameSync(temp, file);
21464
+ fs20.writeFileSync(temp, JSON.stringify(marker));
21465
+ fs20.renameSync(temp, file);
20583
21466
  } catch {}
20584
21467
  };
20585
21468
 
@@ -20588,27 +21471,42 @@ var NONE3 = { kind: "none" };
20588
21471
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20589
21472
  var canonical2 = (file) => {
20590
21473
  let rest = "";
20591
- for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
21474
+ for (let dir = path26.resolve(file);; dir = path26.dirname(dir)) {
20592
21475
  try {
20593
- return path23.join(realpathSync5(dir), rest);
21476
+ return path26.join(realpathSync5(dir), rest);
20594
21477
  } catch {
20595
- if (path23.dirname(dir) === dir)
20596
- return path23.resolve(file);
20597
- 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);
20598
21481
  }
20599
21482
  }
20600
21483
  };
20601
21484
  var exempt = (cwd, root, file, plans) => {
20602
21485
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20603
- const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20604
- 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))
20605
21488
  return true;
20606
- const posix = relative.split(path23.sep).join("/");
21489
+ const posix = relative.split(path26.sep).join("/");
20607
21490
  const segments = posix.split("/");
20608
21491
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20609
21492
  };
20610
21493
  var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
20611
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
+ };
20612
21510
  var editsOf = (input) => {
20613
21511
  let words = input;
20614
21512
  while (words.length && (PREFIXES2.has(words[0]) || /^\w+=/.test(words[0])))
@@ -20685,10 +21583,18 @@ var editsOf = (input) => {
20685
21583
  return [];
20686
21584
  }
20687
21585
  };
20688
- var shellWrites = (command) => {
21586
+ var shellWrites = (command, depth = 0) => {
20689
21587
  const targets = [];
20690
21588
  let unknown = false;
20691
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
+ }
20692
21598
  targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
20693
21599
  const edits = editsOf(segment.words);
20694
21600
  if (edits === null)
@@ -20701,7 +21607,7 @@ var shellWrites = (command) => {
20701
21607
  return { writes: true, targets: unknown ? null : targets };
20702
21608
  };
20703
21609
  var cache2 = new Map;
20704
- 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");
20705
21611
  var gateFor = (cwd) => {
20706
21612
  const store = new TaskStore(cwd);
20707
21613
  const listed = store.listTaskIndex();
@@ -20780,7 +21686,7 @@ var gitMutationDirs = (cwd, command, dialect) => {
20780
21686
  return [];
20781
21687
  if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20782
21688
  return [];
20783
- return [path23.resolve(cwd, invocation.dir ?? ".")];
21689
+ return [path26.resolve(cwd, invocation.dir ?? ".")];
20784
21690
  });
20785
21691
  };
20786
21692
  var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
@@ -20790,15 +21696,15 @@ var cdInto = (dir, words) => {
20790
21696
  const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20791
21697
  if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20792
21698
  return;
20793
- return target === undefined ? os7.homedir() : path23.resolve(dir, target);
21699
+ return target === undefined ? os10.homedir() : path26.resolve(dir, target);
20794
21700
  };
20795
- var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20796
- var segmentTargets = (cwd, command, dialect) => {
21701
+ var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
21702
+ var segmentTargets = (cwd, command, dialect, depth = 0) => {
20797
21703
  const targets = [];
20798
21704
  let dir = cwd;
20799
21705
  let stack = [];
20800
21706
  const groups = [];
20801
- 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);
20802
21708
  for (const segment of segmentsOf(command, dialect)) {
20803
21709
  if (segment.group === "open") {
20804
21710
  groups.push({ dir, stack: [...stack] });
@@ -20826,6 +21732,9 @@ var segmentTargets = (cwd, command, dialect) => {
20826
21732
  dir = stack.length ? stack.pop() : undefined;
20827
21733
  continue;
20828
21734
  }
21735
+ const inner = depth < 4 ? wrappedScript(words) : null;
21736
+ if (inner !== null && dir !== undefined)
21737
+ targets.push(...segmentTargets(dir, inner, "posix", depth + 1));
20829
21738
  for (const file of segment.redirects)
20830
21739
  if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20831
21740
  const resolved = at(file);
@@ -20835,15 +21744,15 @@ var segmentTargets = (cwd, command, dialect) => {
20835
21744
  const edits = editsOf(segment.words);
20836
21745
  if (edits === null) {
20837
21746
  if (dir !== undefined)
20838
- targets.push(path23.join(dir, DIR_TARGET));
21747
+ targets.push(path26.join(dir, DIR_TARGET));
20839
21748
  } else
20840
21749
  for (const file of edits) {
20841
21750
  const resolved = at(file);
20842
21751
  if (resolved)
20843
21752
  targets.push(resolved);
20844
21753
  }
20845
- 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), "")))
20846
- 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));
20847
21756
  }
20848
21757
  return targets;
20849
21758
  };
@@ -20878,7 +21787,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20878
21787
  var guarded = (targets) => {
20879
21788
  const seen = new Set;
20880
21789
  return targets.filter((target) => {
20881
- const dir = path23.dirname(target);
21790
+ const dir = path26.dirname(target);
20882
21791
  if (seen.has(dir))
20883
21792
  return false;
20884
21793
  seen.add(dir);
@@ -20899,7 +21808,7 @@ var claimAll = (owner, targets, deps) => {
20899
21808
  for (const target of guarded(targets))
20900
21809
  claimLiveSession(target, owner, deps);
20901
21810
  };
20902
- 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)];
20903
21812
  function writeGate(cwd, paths, owner, deps) {
20904
21813
  if (!owner)
20905
21814
  return beforeWriteGate(cwd, paths);
@@ -20919,7 +21828,7 @@ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20919
21828
  try {
20920
21829
  targets = [
20921
21830
  ...segmentTargets(cwd, command, dialect),
20922
- ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
21831
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path26.join(dir, DIR_TARGET))
20923
21832
  ];
20924
21833
  } catch {
20925
21834
  targets = [];
@@ -21353,12 +22262,12 @@ ${line}` };
21353
22262
  };
21354
22263
  // packages/workit-core/src/hooks/hosts/fields.ts
21355
22264
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
21356
- import path24 from "node:path";
21357
- 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);
21358
22267
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
21359
22268
  var optionalText = (value) => typeof value === "string" ? value : null;
21360
22269
  var existingDirectory = (value) => {
21361
- if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
22270
+ if (!nonEmpty2(value) || !path27.isAbsolute(value) || !existsSync7(value))
21362
22271
  return null;
21363
22272
  try {
21364
22273
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -21411,7 +22320,7 @@ var isWriteTool = (name) => typeof name === "string" && WRITE_TOOLS.has(name.toL
21411
22320
  var PATH_KEYS = ["file_path", "filePath", "notebook_path", "path", "target_file", "file"];
21412
22321
  var PATCH_HEADER = /^[ \t]*\*\*\* (?:(?:Add|Update|Delete) File|Move to): (.+)$/gm;
21413
22322
  var writePaths = (input) => {
21414
- if (!isRecord4(input))
22323
+ if (!isRecord6(input))
21415
22324
  return [];
21416
22325
  const paths = PATH_KEYS.map((key) => input[key]).filter(nonEmpty2);
21417
22326
  for (const value of Object.values(input))
@@ -21506,10 +22415,32 @@ function handleHook(input, deps) {
21506
22415
  const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
21507
22416
  if (gate.kind === "deny")
21508
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;
21509
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;
21510
22437
  if (descriptor.transport !== "hook-process")
21511
- return NONE5;
21512
- 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
+ `));
21513
22444
  }
21514
22445
  case "shell.post":
21515
22446
  if (usable2(descriptor.events["shell.post"].support))
@@ -21567,6 +22498,112 @@ var dispatchHook = (adapter, raw, env = process.env) => {
21567
22498
  };
21568
22499
  }
21569
22500
  };
22501
+ // packages/workit-core/src/hooks/deadline.ts
22502
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
22503
+ import path28 from "node:path";
22504
+ var HOOK_SKIP_TEXT = "workit context skipped: host under load";
22505
+ var HOOK_DEADLINE_MS = 8000;
22506
+ var HOST_DEADLINES = {
22507
+ claude_code: { "": 8000, SessionStart: 16000, Stop: 16000 },
22508
+ codex_cli: { "": 45000, Stop: 16000 },
22509
+ codex_desktop: { "": 45000, Stop: 16000 },
22510
+ cursor: { "": 45000 }
22511
+ };
22512
+ var HOOK_SKIPS_FILE = "hook-skips.jsonl";
22513
+ var override = (env) => {
22514
+ const value = Number(env.WORKIT_HOOK_DEADLINE_MS);
22515
+ return Number.isFinite(value) && value > 0 ? value : null;
22516
+ };
22517
+ var hookDeadlineMs = (env, host = "", native) => {
22518
+ const row = HOST_DEADLINES[host];
22519
+ return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
22520
+ };
22521
+ var skipsFile = (stateDir) => path28.join(stateDir, HOOK_SKIPS_FILE);
22522
+ var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
22523
+ try {
22524
+ mkdirSync3(stateDir, { recursive: true });
22525
+ appendFileSync(skipsFile(stateDir), `${JSON.stringify(skip)}
22526
+ `);
22527
+ } catch {}
22528
+ };
22529
+ var QUIET = new Set([
22530
+ "stop",
22531
+ "subagent.stop",
22532
+ "shell.post",
22533
+ "compact.pre",
22534
+ "prompt.submit",
22535
+ null
22536
+ ]);
22537
+ var skipDecision = (event) => QUIET.has(event) ? { kind: "none" } : { kind: "context", text: HOOK_SKIP_TEXT };
22538
+ var TIMED_OUT = Symbol("timed out");
22539
+ var within = async (work, ms) => {
22540
+ let timer;
22541
+ const expired = new Promise((resolve) => {
22542
+ timer = setTimeout(() => resolve(TIMED_OUT), Math.max(0, ms));
22543
+ timer.unref?.();
22544
+ });
22545
+ try {
22546
+ return await Promise.race([work, expired]);
22547
+ } finally {
22548
+ clearTimeout(timer);
22549
+ }
22550
+ };
22551
+ var isTimedOut = (value) => value === TIMED_OUT;
22552
+ var PROCESS_START = performance.timeOrigin;
22553
+ var hookBudget = (env, options = {}, host = "") => {
22554
+ const now = options.now ?? Date.now;
22555
+ return {
22556
+ now,
22557
+ record: options.record ?? ((skip) => recordHookSkip(skip)),
22558
+ startedAt: options.startedAt ?? now(),
22559
+ deadlineMs: options.deadlineMs ?? hookDeadlineMs(env, host),
22560
+ host: options.deadlineMs === undefined ? host : null
22561
+ };
22562
+ };
22563
+ var readWithin = async (stdin, budget) => {
22564
+ const read = (async () => {
22565
+ let text = "";
22566
+ for await (const chunk of stdin)
22567
+ text += String(chunk);
22568
+ return text;
22569
+ })();
22570
+ const text = await within(read, budget.startedAt + budget.deadlineMs - budget.now());
22571
+ return isTimedOut(text) ? null : text;
22572
+ };
22573
+ var note = (adapter, raw, env, budget, outcome, stage) => {
22574
+ const parsed = raw === undefined ? null : adapter.parse(raw, env);
22575
+ const event = parsed ? parsed.ok ? parsed.input.event.kind : parsed.event : null;
22576
+ budget.record({
22577
+ at: new Date(budget.now()).toISOString(),
22578
+ host: adapter.descriptor.host,
22579
+ event,
22580
+ outcome,
22581
+ stage,
22582
+ elapsedMs: Math.round(budget.now() - budget.startedAt)
22583
+ });
22584
+ return { native: parsed?.native ?? null, event };
22585
+ };
22586
+ var skippedHook = (adapter, raw, env, budget, stage) => {
22587
+ const { native, event } = note(adapter, raw, env, budget, "skip", stage);
22588
+ return {
22589
+ ...adapter.render(skipDecision(event), native),
22590
+ error: `${HOOK_SKIP_TEXT} (past the ${budget.deadlineMs} ms hook deadline)`,
22591
+ skipped: true
22592
+ };
22593
+ };
22594
+ var dispatchWithin = async (adapter, raw, env, budget, dispatch = dispatchHook) => {
22595
+ const native = raw?.hook_event_name;
22596
+ if (budget.host !== null && typeof native === "string")
22597
+ budget.deadlineMs = hookDeadlineMs(env, budget.host, native);
22598
+ const remaining = () => budget.startedAt + budget.deadlineMs - budget.now();
22599
+ const result = remaining() > 0 ? await within(Promise.resolve().then(() => dispatch(adapter, raw, env)), remaining()) : TIMED_OUT;
22600
+ if (isTimedOut(result))
22601
+ return skippedHook(adapter, raw, env, budget, "dispatch");
22602
+ if (remaining() < 0)
22603
+ note(adapter, raw, env, budget, "late", "done");
22604
+ return { ...result, skipped: false };
22605
+ };
22606
+
21570
22607
  // packages/workit-core/src/hooks/hosts/claude-code.ts
21571
22608
  var CLAUDE_CODE_DESCRIPTOR = {
21572
22609
  host: "claude_code",
@@ -21587,7 +22624,12 @@ var CLAUDE_CODE_DESCRIPTOR = {
21587
22624
  "compact.pre": { support: "none", native: null },
21588
22625
  stop: { support: "native", native: "Stop" }
21589
22626
  },
21590
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22627
+ shellPolicy: {
22628
+ deny: "native",
22629
+ channel: "permissionDecision",
22630
+ failClosed: false,
22631
+ rewrite: "native"
22632
+ },
21591
22633
  context: {
21592
22634
  sessionStart: "native",
21593
22635
  perTurn: "native",
@@ -21669,7 +22711,7 @@ var EVENTS = {
21669
22711
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
21670
22712
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
21671
22713
  var dialectOf2 = (value) => value.tool_name === "PowerShell" ? "powershell" : "posix";
21672
- var toolCommand = (value) => isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
22714
+ var toolCommand = (value) => isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
21673
22715
  var eventOf = (name, value) => {
21674
22716
  const toolUseId = optionalText(value.tool_use_id);
21675
22717
  switch (name) {
@@ -21697,19 +22739,28 @@ var eventOf = (name, value) => {
21697
22739
  kind: "tool.pre",
21698
22740
  tool: value.tool_name,
21699
22741
  toolUseId,
21700
- skill: isRecord4(value.tool_input) ? optionalText(value.tool_input.skill) : null
22742
+ skill: isRecord6(value.tool_input) ? optionalText(value.tool_input.skill) : null
21701
22743
  }
21702
22744
  };
21703
22745
  if (!SHELL_TOOLS.has(value.tool_name))
21704
22746
  return { ok: true, event: { kind: "tool.pre", tool: value.tool_name, toolUseId } };
21705
22747
  const command = toolCommand(value);
21706
- 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}` };
22748
+ return command ? {
22749
+ ok: true,
22750
+ event: {
22751
+ kind: "shell.pre",
22752
+ command,
22753
+ toolUseId,
22754
+ dialect: dialectOf2(value),
22755
+ toolInput: value.tool_input
22756
+ }
22757
+ } : { ok: false, error: `tool_input.command is required for ${value.tool_name}` };
21707
22758
  }
21708
22759
  case "PostToolUse": {
21709
22760
  const command = toolCommand(value);
21710
22761
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
21711
22762
  return { ok: false, error: "only shell PostToolUse is mapped" };
21712
- const response = isRecord4(value.tool_response) ? value.tool_response : {};
22763
+ const response = isRecord6(value.tool_response) ? value.tool_response : {};
21713
22764
  return {
21714
22765
  ok: true,
21715
22766
  event: {
@@ -21767,6 +22818,17 @@ var CONTEXT_EVENTS = new Set([
21767
22818
  "PostToolUse"
21768
22819
  ]);
21769
22820
  var render = (decision, native) => {
22821
+ if (decision.kind === "rewrite" && native === "PreToolUse" && decision.input)
22822
+ return {
22823
+ json: {
22824
+ hookSpecificOutput: {
22825
+ hookEventName: "PreToolUse",
22826
+ updatedInput: decision.input,
22827
+ ...decision.text ? { additionalContext: decision.text } : {}
22828
+ }
22829
+ },
22830
+ exitCode: 0
22831
+ };
21770
22832
  if (decision.kind === "deny" && native === "PreToolUse")
21771
22833
  return {
21772
22834
  json: {
@@ -21792,14 +22854,14 @@ var render = (decision, native) => {
21792
22854
  var claudeCodeAdapter = {
21793
22855
  descriptor: CLAUDE_CODE_DESCRIPTOR,
21794
22856
  parse(raw) {
21795
- const native = isRecord4(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22857
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
21796
22858
  const fail = (error) => ({
21797
22859
  ok: false,
21798
22860
  error,
21799
22861
  native,
21800
22862
  event: native ? EVENTS[native] : null
21801
22863
  });
21802
- if (!isRecord4(raw) || !native)
22864
+ if (!isRecord6(raw) || !native)
21803
22865
  return fail("hook_event_name is required");
21804
22866
  if (!nonEmpty2(raw.session_id))
21805
22867
  return fail("session_id is required");
@@ -21853,7 +22915,12 @@ var CODEX_DESCRIPTOR = {
21853
22915
  "compact.pre": { support: "none", native: null },
21854
22916
  stop: { support: "native", native: "Stop" }
21855
22917
  },
21856
- shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
22918
+ shellPolicy: {
22919
+ deny: "native",
22920
+ channel: "permissionDecision",
22921
+ failClosed: false,
22922
+ rewrite: "none"
22923
+ },
21857
22924
  context: {
21858
22925
  sessionStart: "native",
21859
22926
  perTurn: "native",
@@ -21945,7 +23012,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
21945
23012
  var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
21946
23013
  var SOURCES2 = new Set(["startup", "resume", "clear", "compact", "fork"]);
21947
23014
  var parseCodexHookInput = (value) => {
21948
- if (!isRecord4(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
23015
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
21949
23016
  return { ok: false, error: "hook_event_name is required" };
21950
23017
  const event = value.hook_event_name;
21951
23018
  const cwd = existingDirectory(value.cwd);
@@ -21954,7 +23021,7 @@ var parseCodexHookInput = (value) => {
21954
23021
  const toolEvent = event === "PreToolUse" || event === "PostToolUse";
21955
23022
  if (toolEvent && !nonEmpty2(value.tool_name))
21956
23023
  return { ok: false, error: "tool_name is required" };
21957
- const command = isRecord4(value.tool_input) ? commandText(value.tool_input.command) : null;
23024
+ const command = isRecord6(value.tool_input) ? commandText(value.tool_input.command) : null;
21958
23025
  if (toolEvent && isShellTool(value.tool_name) && !command)
21959
23026
  return { ok: false, error: "tool_input.command is required for shell tools" };
21960
23027
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -22068,7 +23135,7 @@ var codexAdapter = {
22068
23135
  const host = detectCodexSurface(env);
22069
23136
  const parsed = parseCodexHookInput(raw);
22070
23137
  if (!parsed.ok) {
22071
- const native = isRecord4(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
23138
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22072
23139
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
22073
23140
  }
22074
23141
  const input = parsed.data;
@@ -22095,8 +23162,8 @@ var codexAdapter = {
22095
23162
  };
22096
23163
 
22097
23164
  // packages/workit-core/src/hooks/hosts/cursor.ts
22098
- import fs18 from "node:fs";
22099
- import path25 from "node:path";
23165
+ import fs21 from "node:fs";
23166
+ import path29 from "node:path";
22100
23167
  var undocumented = { support: "undocumented", native: null };
22101
23168
  var none = { support: "none", native: null };
22102
23169
  var CURSOR_DESCRIPTOR = {
@@ -22118,7 +23185,7 @@ var CURSOR_DESCRIPTOR = {
22118
23185
  "compact.pre": { support: "partial", native: "preCompact" },
22119
23186
  stop: { support: "native", native: "stop" }
22120
23187
  },
22121
- shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false },
23188
+ shellPolicy: { deny: "native", channel: "exit2+json", failClosed: false, rewrite: "none" },
22122
23189
  context: {
22123
23190
  sessionStart: "native",
22124
23191
  perTurn: "none",
@@ -22211,7 +23278,7 @@ var EVENTS3 = {
22211
23278
  stop: "stop"
22212
23279
  };
22213
23280
  var parseCursorHookInput = (value) => {
22214
- if (!isRecord4(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
23281
+ if (!isRecord6(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
22215
23282
  return { ok: false, error: "hook_event_name is required" };
22216
23283
  const roots = value.workspace_roots;
22217
23284
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -22293,17 +23360,17 @@ var protocolEvent2 = (input) => {
22293
23360
  };
22294
23361
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
22295
23362
  var lastAssistantText = (file) => {
22296
- if (!file || !path25.isAbsolute(file))
23363
+ if (!file || !path29.isAbsolute(file))
22297
23364
  return null;
22298
23365
  try {
22299
- const size = fs18.statSync(file).size;
23366
+ const size = fs21.statSync(file).size;
22300
23367
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
22301
23368
  const buffer = Buffer.alloc(size - start);
22302
- const fd = fs18.openSync(file, "r");
23369
+ const fd = fs21.openSync(file, "r");
22303
23370
  try {
22304
- fs18.readSync(fd, buffer, 0, buffer.length, start);
23371
+ fs21.readSync(fd, buffer, 0, buffer.length, start);
22305
23372
  } finally {
22306
- fs18.closeSync(fd);
23373
+ fs21.closeSync(fd);
22307
23374
  }
22308
23375
  const lines = buffer.toString("utf8").split(`
22309
23376
  `);
@@ -22314,14 +23381,14 @@ var lastAssistantText = (file) => {
22314
23381
  } catch {
22315
23382
  continue;
22316
23383
  }
22317
- if (!isRecord4(entry) || entry.role !== "assistant")
23384
+ if (!isRecord6(entry) || entry.role !== "assistant")
22318
23385
  continue;
22319
- const content = isRecord4(entry.message) ? entry.message.content : entry.content;
23386
+ const content = isRecord6(entry.message) ? entry.message.content : entry.content;
22320
23387
  if (typeof content === "string")
22321
23388
  return content;
22322
23389
  if (!Array.isArray(content))
22323
23390
  return null;
22324
- const text = content.filter((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
23391
+ const text = content.filter((part) => isRecord6(part) && part.type === "text" && typeof part.text === "string").map((part) => part.text).join(`
22325
23392
  `);
22326
23393
  return text || null;
22327
23394
  }
@@ -22330,7 +23397,7 @@ var lastAssistantText = (file) => {
22330
23397
  return null;
22331
23398
  }
22332
23399
  };
22333
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
23400
+ var within2 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path29.sep) ? root : `${root}${path29.sep}`);
22334
23401
  var hookCwd = (input) => {
22335
23402
  const cwd = existingDirectory(input.cwd);
22336
23403
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -22339,7 +23406,7 @@ var hookCwd = (input) => {
22339
23406
  };
22340
23407
  var cursorWorkspaceRoot = (input) => {
22341
23408
  const cwd = existingDirectory(input.cwd);
22342
- return cwd && input.workspace_roots.find((root) => within(root, cwd)) || input.workspace_roots[0];
23409
+ return cwd && input.workspace_roots.find((root) => within2(root, cwd)) || input.workspace_roots[0];
22343
23410
  };
22344
23411
  var cursorDeny = (reason) => ({
22345
23412
  permission: "deny",
@@ -22371,7 +23438,7 @@ var cursorAdapter = {
22371
23438
  parse(raw) {
22372
23439
  const parsed = parseCursorHookInput(raw);
22373
23440
  if (!parsed.ok) {
22374
- const native = isRecord4(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
23441
+ const native = isRecord6(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
22375
23442
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
22376
23443
  }
22377
23444
  const input = parsed.data;
@@ -22404,19 +23471,21 @@ var HOOK_ADAPTERS = {
22404
23471
  codex_desktop: codexAdapter,
22405
23472
  cursor: cursorAdapter
22406
23473
  };
22407
- async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env) {
23474
+ async function runHookProcess(host, stdin, stdout, stderr = process.stderr, env = process.env, deadline = {}) {
22408
23475
  const adapter = typeof host === "string" ? HOOK_ADAPTERS[host] : host;
22409
- let text = "";
22410
- for await (const chunk of stdin)
22411
- text += String(chunk);
23476
+ const budget = hookBudget(env, deadline, adapter.descriptor.host);
23477
+ const text = await readWithin(stdin, budget);
22412
23478
  let raw;
22413
23479
  try {
22414
- raw = JSON.parse(text || "{}");
23480
+ raw = text === null ? undefined : JSON.parse(text || "{}");
22415
23481
  } catch {
22416
23482
  raw = undefined;
22417
23483
  }
22418
- const result = dispatchHook(adapter, raw, env);
22419
- if (raw === undefined || result.error)
23484
+ const result = text === null ? skippedHook(adapter, undefined, env, budget, "input") : await dispatchWithin(adapter, raw, env, budget, deadline.dispatch);
23485
+ if (result.skipped)
23486
+ stderr.write(`[workit] ${result.error}
23487
+ `);
23488
+ else if (raw === undefined || result.error)
22420
23489
  stderr.write(`[workit] hook input rejected: ${raw === undefined ? "invalid JSON hook input" : result.error}
22421
23490
  `);
22422
23491
  stdout.write(`${JSON.stringify(result.json)}
@@ -22569,6 +23638,15 @@ var degradedCursorHook = (raw) => {
22569
23638
  return { json: { permission: "allow", user_message: note, agent_message: note } };
22570
23639
  };
22571
23640
  var handleCursorHook = (raw) => degradedCursorHook(raw)?.json ?? (isSubagentStart(raw) ? handleSubagentStartHook(raw) : dispatchHook(cursorAdapter, raw).json);
23641
+ var subagentStartStep = (_adapter, raw) => {
23642
+ let json;
23643
+ try {
23644
+ json = handleSubagentStartHook(raw);
23645
+ } catch (error) {
23646
+ json = cursorDeny(`hook failure: ${String(error)}`);
23647
+ }
23648
+ return { json, exitCode: json.permission === "deny" ? 2 : 0, error: null };
23649
+ };
22572
23650
  var runCursorHook = async () => {
22573
23651
  let text = "";
22574
23652
  for await (const chunk of process.stdin)
@@ -22586,19 +23664,10 @@ var runCursorHook = async () => {
22586
23664
  process.exitCode = 0;
22587
23665
  return;
22588
23666
  }
22589
- if (isSubagentStart(raw)) {
22590
- let output;
22591
- try {
22592
- output = handleSubagentStartHook(raw);
22593
- } catch (error) {
22594
- output = cursorDeny(`hook failure: ${String(error)}`);
22595
- }
22596
- process.stdout.write(`${JSON.stringify(output)}
22597
- `);
22598
- process.exitCode = output.permission === "deny" ? 2 : 0;
22599
- return;
22600
- }
22601
- process.exitCode = await runHookProcess(cursorAdapter, [text], process.stdout);
23667
+ process.exitCode = await runHookProcess(cursorAdapter, [text], process.stdout, process.stderr, process.env, {
23668
+ startedAt: PROCESS_START,
23669
+ ...isSubagentStart(raw) ? { dispatch: subagentStartStep } : {}
23670
+ });
22602
23671
  };
22603
23672
  if (__require.main == __require.module)
22604
23673
  await runCursorHook();