@brainervirus/workit-cursor 2.6.0 → 2.8.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.
@@ -7899,7 +7899,37 @@ var candidateSchema = object({
7899
7899
  path: ["id"]
7900
7900
  });
7901
7901
  });
7902
- var evidenceSchema = object({
7902
+ var checkObservationSchema = object({
7903
+ observer: literal("workit_cli"),
7904
+ name: nonEmpty.nullable(),
7905
+ configured: boolean2(),
7906
+ argv: array(text).min(1),
7907
+ shell: boolean2(),
7908
+ cwd: nonEmpty,
7909
+ exitCode: safeInteger,
7910
+ durationMs: safeInteger.min(0),
7911
+ timedOut: boolean2(),
7912
+ head: text.nullable(),
7913
+ tree: text.nullable(),
7914
+ dirty: boolean2().nullable(),
7915
+ signal: text.nullable(),
7916
+ treeAfter: text.nullable(),
7917
+ modifiedWorktree: boolean2(),
7918
+ base: text.nullable(),
7919
+ patchId: text.nullable(),
7920
+ environment: object({
7921
+ platform: nonEmpty,
7922
+ arch: nonEmpty,
7923
+ vars: record(nonEmpty, text.nullable())
7924
+ }).strict(),
7925
+ logDigest: text.nullable(),
7926
+ logRef: text.nullable(),
7927
+ logTail: array(text).max(40),
7928
+ ledgerRowId: text.nullable(),
7929
+ attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
7930
+ }).strict();
7931
+ var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
7932
+ var evidenceFields = {
7903
7933
  kind: _enum(["check", "review", "investigation", "artifact"]),
7904
7934
  claim: text,
7905
7935
  requirementIds: array(digest),
@@ -7910,7 +7940,9 @@ var evidenceSchema = object({
7910
7940
  refs: array(refSchema),
7911
7941
  exitCode: safeInteger.nullable(),
7912
7942
  reviewContext: refSchema.nullable()
7913
- }).strict();
7943
+ };
7944
+ var evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
7945
+ var reportedEvidenceSchema = object(evidenceFields).strict();
7914
7946
  var evidenceEvaluationSchema = object({
7915
7947
  evidenceId: id,
7916
7948
  status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
@@ -8221,7 +8253,7 @@ var evidenceOperations = {
8221
8253
  action: literal("record"),
8222
8254
  ...taskId,
8223
8255
  expectedRevision: revision.optional(),
8224
- evidence: evidenceSchema
8256
+ evidence: reportedEvidenceSchema
8225
8257
  })
8226
8258
  };
8227
8259
  var findingOperations = {
@@ -12462,19 +12494,351 @@ function diffPolicy(previous, next, reason, now) {
12462
12494
  }
12463
12495
 
12464
12496
  // packages/workit-core/src/core/task-evaluation.ts
12465
- import { createHash as createHash7 } from "node:crypto";
12466
- import * as fs8 from "node:fs";
12497
+ import { createHash as createHash8 } from "node:crypto";
12498
+ import * as fs10 from "node:fs";
12499
+ import { spawnSync as spawnSync5 } from "node:child_process";
12500
+ import path12 from "node:path";
12501
+
12502
+ // packages/workit-core/src/check-config.ts
12467
12503
  import { spawnSync as spawnSync3 } from "node:child_process";
12504
+ import fs8 from "node:fs";
12468
12505
  import path10 from "node:path";
12469
- var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex");
12470
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
12471
- var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
12506
+ var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
12507
+ var CHECKS_FILE = "workit.checks.json";
12508
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12509
+ var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
12510
+ function splitCommand(command) {
12511
+ const argv = [];
12512
+ let current = "";
12513
+ let quote = null;
12514
+ let started = false;
12515
+ for (const char of command) {
12516
+ if (quote) {
12517
+ if (char === quote)
12518
+ quote = null;
12519
+ else
12520
+ current += char;
12521
+ continue;
12522
+ }
12523
+ if (char === '"' || char === "'") {
12524
+ quote = char;
12525
+ started = true;
12526
+ continue;
12527
+ }
12528
+ if (/\s/u.test(char)) {
12529
+ if (started)
12530
+ argv.push(current);
12531
+ current = "";
12532
+ started = false;
12533
+ continue;
12534
+ }
12535
+ if (SHELL_META.test(char))
12536
+ return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
12537
+ current += char;
12538
+ started = true;
12539
+ }
12540
+ if (quote)
12541
+ return new Error(`"${command}" has an unterminated quote`);
12542
+ if (started)
12543
+ argv.push(current);
12544
+ return argv.length ? argv : new Error("empty command");
12545
+ }
12546
+ var git = (cwd, args) => {
12547
+ const run = spawnSync3("git", args, {
12548
+ cwd,
12549
+ encoding: "utf8",
12550
+ timeout: 20000,
12551
+ killSignal: "SIGKILL",
12552
+ windowsHide: true
12553
+ });
12554
+ const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
12555
+ return value || null;
12556
+ };
12557
+ function checkRoot(cwd) {
12558
+ return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
12559
+ }
12560
+ var packageManager = (root) => {
12561
+ const has = (file) => fs8.existsSync(path10.join(root, file));
12562
+ if (has("bun.lock") || has("bun.lockb"))
12563
+ return "bun";
12564
+ if (has("pnpm-lock.yaml"))
12565
+ return "pnpm";
12566
+ if (has("yarn.lock"))
12567
+ return "yarn";
12568
+ return "npm";
12569
+ };
12570
+ var scriptCheck = (pm, name) => {
12571
+ const argv = [pm, "run", name];
12572
+ const accepts = [argv];
12573
+ if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
12574
+ accepts.push([pm, name]);
12575
+ return { name, argv, accepts };
12576
+ };
12577
+ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
12578
+ function detectedChecks(root) {
12579
+ const read = (file) => {
12580
+ try {
12581
+ return fs8.readFileSync(path10.join(root, file), "utf8");
12582
+ } catch {
12583
+ return null;
12584
+ }
12585
+ };
12586
+ const checks = [];
12587
+ const add = (check) => {
12588
+ if (!checks.some((item) => item.name === check.name))
12589
+ checks.push(check);
12590
+ };
12591
+ const manifestText = read("package.json");
12592
+ if (manifestText !== null) {
12593
+ let manifest = null;
12594
+ try {
12595
+ manifest = JSON.parse(manifestText);
12596
+ } catch {}
12597
+ const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
12598
+ const pm = packageManager(root);
12599
+ for (const name of SCRIPT_CHECKS) {
12600
+ const script = scripts[name];
12601
+ if (typeof script === "string" && script.trim() !== "")
12602
+ add(scriptCheck(pm, name));
12603
+ }
12604
+ }
12605
+ const plain = (name, argv) => ({ name, argv, accepts: [argv] });
12606
+ if (read("go.mod") !== null)
12607
+ add(plain("test", ["go", "test", "./..."]));
12608
+ if (read("Cargo.toml") !== null)
12609
+ add(plain("test", ["cargo", "test"]));
12610
+ if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
12611
+ add(plain("test", ["pytest"]));
12612
+ const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
12613
+ if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
12614
+ add(plain("test", ["make", "test"]));
12615
+ return checks;
12616
+ }
12617
+ var GATES = ["testing", "verification"];
12618
+ function loadCheckConfig(cwd) {
12619
+ const root = checkRoot(cwd);
12620
+ const file = path10.join(root, CHECKS_FILE);
12621
+ const none = (error = null) => ({
12622
+ source: error ? CHECKS_FILE : "none",
12623
+ root,
12624
+ checks: [],
12625
+ gates: {},
12626
+ error
12627
+ });
12628
+ let text = null;
12629
+ try {
12630
+ text = fs8.readFileSync(file, "utf8");
12631
+ } catch (error) {
12632
+ if (error.code !== "ENOENT")
12633
+ return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
12634
+ }
12635
+ if (text === null) {
12636
+ const checks = detectedChecks(root);
12637
+ return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
12638
+ }
12639
+ let parsed;
12640
+ try {
12641
+ parsed = JSON.parse(text);
12642
+ } catch {
12643
+ return none(`${CHECKS_FILE} is not valid JSON`);
12644
+ }
12645
+ if (!isRecord2(parsed) || !isRecord2(parsed.checks))
12646
+ return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
12647
+ const checks = [];
12648
+ for (const [name, value] of Object.entries(parsed.checks)) {
12649
+ if (!CHECK_NAME.test(name))
12650
+ return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
12651
+ const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
12652
+ if (argv instanceof Error)
12653
+ return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
12654
+ checks.push({ name, argv, accepts: [argv] });
12655
+ }
12656
+ const gates = {};
12657
+ if (parsed.gates !== undefined) {
12658
+ if (!isRecord2(parsed.gates))
12659
+ return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
12660
+ for (const [gate, value] of Object.entries(parsed.gates)) {
12661
+ if (!GATES.includes(gate))
12662
+ return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
12663
+ const names = typeof value === "string" ? [value] : value;
12664
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
12665
+ return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
12666
+ gates[gate] = names;
12667
+ }
12668
+ }
12669
+ return { source: CHECKS_FILE, root, checks, gates, error: null };
12670
+ }
12671
+ var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
12672
+ function matchesNamedCheck(config, name, argv, cwd) {
12673
+ if (config.error || name === null || cwd !== ".")
12674
+ return false;
12675
+ const check = config.checks.find((item) => item.name === name);
12676
+ return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
12677
+ }
12678
+ function gateCheckNames(config, dimension) {
12679
+ const explicit = config.gates[dimension];
12680
+ if (explicit)
12681
+ return explicit;
12682
+ const names = config.checks.map((check) => check.name);
12683
+ if (dimension === "testing")
12684
+ return names.includes("test") ? ["test"] : [];
12685
+ return names;
12686
+ }
12687
+ var workitOnPath;
12688
+ function workitAvailable(env = process.env) {
12689
+ if (workitOnPath !== undefined)
12690
+ return workitOnPath;
12691
+ const windows = process.platform === "win32";
12692
+ const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
12693
+ const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
12694
+ workitOnPath = dirs.some((dir) => exts.some((ext) => {
12695
+ try {
12696
+ return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
12697
+ } catch {
12698
+ return false;
12699
+ }
12700
+ }));
12701
+ return workitOnPath;
12702
+ }
12703
+ function checkCommand(args, version, onPath = workitAvailable()) {
12704
+ return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
12705
+ }
12706
+
12707
+ // packages/workit-core/src/git/rev.ts
12708
+ import { spawnSync as spawnSync4 } from "node:child_process";
12709
+ import { createHash as createHash7 } from "node:crypto";
12710
+ import fs9 from "node:fs";
12711
+ import os2 from "node:os";
12712
+ import path11 from "node:path";
12713
+ var MAX_BUFFER = 256 * 1024 * 1024;
12714
+ var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
12715
+ var git2 = (cwd, args, options = {}) => {
12716
+ const result = spawnSync4("git", args, {
12717
+ cwd,
12718
+ encoding: "utf8",
12719
+ env: options.env ?? process.env,
12720
+ input: options.input,
12721
+ maxBuffer: MAX_BUFFER,
12722
+ timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
12723
+ killSignal: "SIGKILL",
12724
+ windowsHide: true
12725
+ });
12726
+ const error = result.error;
12727
+ return {
12728
+ ok: result.status === 0,
12729
+ stdout: result.stdout ?? "",
12730
+ stderr: result.stderr ?? "",
12731
+ timedOut: error?.code === "ETIMEDOUT"
12732
+ };
12733
+ };
12734
+ var line = (run) => {
12735
+ if (!run.ok)
12736
+ return null;
12737
+ const value = run.stdout.trim();
12738
+ return value ? value : null;
12739
+ };
12740
+ function headSha(cwd) {
12741
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
12742
+ }
12743
+ var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
12744
+ var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
12745
+ var seedIndex = (from, to) => {
12746
+ fs9.copyFileSync(from, to);
12747
+ const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
12748
+ const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
12749
+ fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
12750
+ };
12751
+ function worktreeTree(cwd, options = {}) {
12752
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
12753
+ const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
12754
+ if (!top || !indexPath)
12755
+ return null;
12756
+ const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
12757
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
12758
+ const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
12759
+ try {
12760
+ const tempIndex = path11.join(scratch, "index");
12761
+ if (fs9.existsSync(indexPath))
12762
+ seedIndex(indexPath, tempIndex);
12763
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
12764
+ const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
12765
+ env,
12766
+ timeoutMs
12767
+ });
12768
+ if (!untracked.ok)
12769
+ return null;
12770
+ const skipped = [];
12771
+ for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
12772
+ try {
12773
+ const stat = fs9.statSync(path11.join(top, file));
12774
+ if (stat.isFile() && stat.size > cap)
12775
+ skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
12776
+ } catch {}
12777
+ }
12778
+ skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
12779
+ const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
12780
+ env,
12781
+ timeoutMs,
12782
+ input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
12783
+ }) : git2(top, ["add", "-A"], { env, timeoutMs });
12784
+ if (!added.ok)
12785
+ return null;
12786
+ const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
12787
+ if (!tree)
12788
+ return null;
12789
+ const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
12790
+ const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
12791
+ return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
12792
+ } finally {
12793
+ fs9.rmSync(scratch, { recursive: true, force: true });
12794
+ }
12795
+ }
12796
+ function worktreeSignal(cwd, options = {}) {
12797
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
12798
+ if (!top)
12799
+ return null;
12800
+ const status = git2(top, [
12801
+ "status",
12802
+ "--porcelain=v1",
12803
+ "-z",
12804
+ "--untracked-files=all",
12805
+ "--no-renames",
12806
+ "--ignore-submodules=dirty"
12807
+ ], {
12808
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
12809
+ timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
12810
+ });
12811
+ if (!status.ok)
12812
+ return null;
12813
+ const hash = createHash7("sha256");
12814
+ hash.update(`${headSha(top) ?? "unborn"}\x00`);
12815
+ for (const entry of status.stdout.split("\x00")) {
12816
+ if (entry.length < 4)
12817
+ continue;
12818
+ const file = entry.slice(3);
12819
+ const absolute = path11.join(top, file);
12820
+ let stat = "-";
12821
+ try {
12822
+ const info = fs9.lstatSync(absolute, { bigint: true });
12823
+ stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
12824
+ if (info.isDirectory())
12825
+ stat += `:${headSha(absolute) ?? "-"}`;
12826
+ } catch {}
12827
+ hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
12828
+ }
12829
+ return `sig:${hash.digest("hex")}`;
12830
+ }
12831
+
12832
+ // packages/workit-core/src/core/task-evaluation.ts
12833
+ var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex");
12834
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`);
12835
+ var relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".";
12472
12836
  var canonicalPath = (value) => {
12473
12837
  if (typeof value !== "string" || !value)
12474
12838
  return null;
12475
12839
  if (value.includes("\\") || value.split("/").includes(".."))
12476
12840
  return null;
12477
- const normalized = path10.posix.normalize(value);
12841
+ const normalized = path12.posix.normalize(value);
12478
12842
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
12479
12843
  return null;
12480
12844
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -12510,7 +12874,7 @@ var compareCodeUnits3 = (left, right) => {
12510
12874
  return left.length - right.length;
12511
12875
  };
12512
12876
  var gitPaths = (root) => {
12513
- const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
12877
+ const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
12514
12878
  cwd: root,
12515
12879
  encoding: "buffer"
12516
12880
  });
@@ -12522,21 +12886,21 @@ var gitPaths = (root) => {
12522
12886
  git: !/not a git repository/i.test(stderr)
12523
12887
  };
12524
12888
  }
12525
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
12526
- const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
12889
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
12890
+ const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
12527
12891
  if (stagedDeleted.status !== 0) {
12528
12892
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
12529
12893
  if (!/not a git repository/i.test(stderr))
12530
12894
  return { paths, uncertain: true, git: true };
12531
12895
  } else if (stagedDeleted.stdout) {
12532
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
12896
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
12533
12897
  }
12534
12898
  return { paths: [...new Set(paths)], uncertain: false, git: true };
12535
12899
  };
12536
12900
  var walk = (root, directory, output) => {
12537
12901
  let names;
12538
12902
  try {
12539
- names = fs8.readdirSync(directory);
12903
+ names = fs10.readdirSync(directory);
12540
12904
  } catch {
12541
12905
  return true;
12542
12906
  }
@@ -12544,10 +12908,10 @@ var walk = (root, directory, output) => {
12544
12908
  for (const name of names) {
12545
12909
  if (name === ".git" || name === ".workit")
12546
12910
  continue;
12547
- const target = path10.join(directory, name);
12911
+ const target = path12.join(directory, name);
12548
12912
  let stat;
12549
12913
  try {
12550
- stat = fs8.lstatSync(target);
12914
+ stat = fs10.lstatSync(target);
12551
12915
  } catch {
12552
12916
  uncertain = true;
12553
12917
  continue;
@@ -12562,14 +12926,14 @@ var walk = (root, directory, output) => {
12562
12926
  var scopeRoots = (root, scope) => {
12563
12927
  const roots = [];
12564
12928
  for (const item of scope.paths.length ? scope.paths : ["."]) {
12565
- const target = path10.resolve(root, item);
12929
+ const target = path12.resolve(root, item);
12566
12930
  if (!inside(root, target))
12567
12931
  return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
12568
12932
  let ancestor = target;
12569
- while (!fs8.existsSync(ancestor) && ancestor !== root)
12570
- ancestor = path10.dirname(ancestor);
12933
+ while (!fs10.existsSync(ancestor) && ancestor !== root)
12934
+ ancestor = path12.dirname(ancestor);
12571
12935
  try {
12572
- if (!inside(root, fs8.realpathSync(ancestor)))
12936
+ if (!inside(root, fs10.realpathSync(ancestor)))
12573
12937
  return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
12574
12938
  } catch {
12575
12939
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -12581,7 +12945,7 @@ var scopeRoots = (root, scope) => {
12581
12945
  function captureCandidate(root, scope, environment = []) {
12582
12946
  let checkout;
12583
12947
  try {
12584
- checkout = fs8.realpathSync(root);
12948
+ checkout = fs10.realpathSync(root);
12585
12949
  } catch {
12586
12950
  return failure2("invalid_input", "candidate root does not exist");
12587
12951
  }
@@ -12599,11 +12963,11 @@ function captureCandidate(root, scope, environment = []) {
12599
12963
  if (item === ".git" || item === ".workit")
12600
12964
  continue;
12601
12965
  names.add(item);
12602
- if (!fs8.existsSync(target))
12966
+ if (!fs10.existsSync(target))
12603
12967
  continue;
12604
12968
  let stat;
12605
12969
  try {
12606
- stat = fs8.lstatSync(target);
12970
+ stat = fs10.lstatSync(target);
12607
12971
  } catch {
12608
12972
  uncertain = true;
12609
12973
  continue;
@@ -12619,17 +12983,17 @@ function captureCandidate(root, scope, environment = []) {
12619
12983
  for (const item of [...names].toSorted()) {
12620
12984
  if (!scopeMatches(item, normalizedScope))
12621
12985
  continue;
12622
- const target = path10.join(checkout, item);
12986
+ const target = path12.join(checkout, item);
12623
12987
  try {
12624
- const stat = fs8.lstatSync(target);
12988
+ const stat = fs10.lstatSync(target);
12625
12989
  if (stat.isSymbolicLink()) {
12626
- const link = fs8.readlinkSync(target);
12990
+ const link = fs10.readlinkSync(target);
12627
12991
  files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
12628
12992
  } else if (stat.isFile()) {
12629
12993
  files.push({
12630
12994
  path: item,
12631
12995
  kind: "file",
12632
- digest: digestBytes3(fs8.readFileSync(target)),
12996
+ digest: digestBytes3(fs10.readFileSync(target)),
12633
12997
  executable: (stat.mode & 73) !== 0
12634
12998
  });
12635
12999
  } else if (!stat.isDirectory()) {
@@ -12654,7 +13018,7 @@ function captureCandidate(root, scope, environment = []) {
12654
13018
  const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
12655
13019
  if (values.some(({ value }) => value === null))
12656
13020
  completeness = "uncertain";
12657
- const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
13021
+ const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
12658
13022
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
12659
13023
  completeness = "uncertain";
12660
13024
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -12692,7 +13056,24 @@ var sameSession = (left, right) => {
12692
13056
  return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
12693
13057
  };
12694
13058
  var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
12695
- function evaluateEvidence(task, candidate) {
13059
+ var memo2 = (compute) => {
13060
+ let value;
13061
+ return () => {
13062
+ if (value === undefined)
13063
+ value = compute();
13064
+ return value;
13065
+ };
13066
+ };
13067
+ var treeFreshness = (root) => ({
13068
+ mode: "tree",
13069
+ current: memo2(() => worktreeTree(root)?.key ?? null)
13070
+ });
13071
+ var signalFreshness = (root) => ({
13072
+ mode: "signal",
13073
+ current: memo2(() => worktreeSignal(root))
13074
+ });
13075
+ var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
13076
+ function evaluateEvidence(task, candidate, freshness) {
12696
13077
  const current = candidate ?? task.candidates.at(-1) ?? null;
12697
13078
  return task.evidence.map((entry) => {
12698
13079
  const evidence = entry.data;
@@ -12702,6 +13083,25 @@ function evaluateEvidence(task, candidate) {
12702
13083
  status: "failed",
12703
13084
  reason: "failed evidence remains historical"
12704
13085
  };
13086
+ const observation = isObservedCheck(entry) ? evidence.observation : undefined;
13087
+ if (observation && observation.tree !== null) {
13088
+ if (task.status === "closed")
13089
+ return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
13090
+ if (observation.modifiedWorktree)
13091
+ return {
13092
+ evidenceId: entry.id,
13093
+ status: "stale",
13094
+ reason: "the check changed the worktree while it ran; ignore or clean its outputs"
13095
+ };
13096
+ const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
13097
+ if (recorded !== null && freshness && recorded === freshness.current())
13098
+ return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
13099
+ return {
13100
+ evidenceId: entry.id,
13101
+ status: "stale",
13102
+ reason: freshness?.mode === "signal" ? "the worktree may have changed since the check ran (quick signal; inspect re-checks exactly)" : "the worktree changed since the check ran"
13103
+ };
13104
+ }
12705
13105
  if (evidence.kind === "check" || evidence.kind === "review") {
12706
13106
  if (!evidence.beforeCandidateId || !evidence.candidateId)
12707
13107
  return {
@@ -12785,8 +13185,45 @@ var expectedKinds = (dimension, ruleId) => {
12785
13185
  return "decision";
12786
13186
  return "worker report";
12787
13187
  };
12788
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
12789
- const evidence = evaluateEvidence(task, candidate);
13188
+ var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
13189
+ function evaluateObservedGate(task, requirement, statuses, config) {
13190
+ const names = gateCheckNames(config, requirement.dimension);
13191
+ const observed = task.evidence.filter(isObservedCheck);
13192
+ const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
13193
+ const named = (entry) => {
13194
+ const observation = entry.data.observation;
13195
+ return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
13196
+ };
13197
+ const considered = observed.map((entry) => entry.id);
13198
+ const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
13199
+ if (config.error)
13200
+ return {
13201
+ satisfied: [],
13202
+ considered,
13203
+ reason: `check config is invalid (${config.error}); fix it, then run ${run}`
13204
+ };
13205
+ if (!names.length)
13206
+ return {
13207
+ satisfied: [],
13208
+ considered,
13209
+ reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE} (committed, so it shows in the diff), e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
13210
+ };
13211
+ const satisfied = fresh.filter(named);
13212
+ if (satisfied.length)
13213
+ return {
13214
+ satisfied: satisfied.map((entry) => entry.id),
13215
+ considered,
13216
+ reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
13217
+ };
13218
+ const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
13219
+ const reason = fresh.length ? `ad-hoc checks do not satisfy a configured gate; run ${run}` : observed.some((entry) => statuses.get(entry.id) === "stale") ? `observed checks are stale (the worktree changed); run ${run}` : observed.some((entry) => statuses.get(entry.id) === "failed") ? `the observed check failed; fix it and run ${run}` : reported ? `agent-reported checks are notes, not evidence; run ${run}` : `no CLI-observed check (needs kind:check observed by workit check); run ${run}`;
13220
+ return { satisfied: [], considered, reason };
13221
+ }
13222
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
13223
+ const evidence = evaluateEvidence(task, candidate, freshness);
13224
+ const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
13225
+ let checkConfig;
13226
+ const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
12790
13227
  if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
12791
13228
  return task.policy.requirements.map((requirement) => ({
12792
13229
  requirementId: requirement.id,
@@ -12796,8 +13233,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
12796
13233
  reason: "stored policy version is unsupported; reassessment is required"
12797
13234
  }));
12798
13235
  return (task.policy?.requirements ?? []).map((requirement) => {
13236
+ const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
13237
+ if (gate?.satisfied.length)
13238
+ return {
13239
+ requirementId: requirement.id,
13240
+ status: "satisfied",
13241
+ evidenceIds: gate.satisfied,
13242
+ decisionIds: [],
13243
+ reason: gate.reason
13244
+ };
12799
13245
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
12800
13246
  const passed = related.filter(({ entry, evaluation }) => {
13247
+ if (gate)
13248
+ return false;
12801
13249
  if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
12802
13250
  return false;
12803
13251
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
@@ -12860,6 +13308,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
12860
13308
  reason: "an applicable approved limitation permits the missing evidence"
12861
13309
  };
12862
13310
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
13311
+ if (gate)
13312
+ return {
13313
+ requirementId: requirement.id,
13314
+ status: unavailable ? "unavailable" : "unsatisfied",
13315
+ evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
13316
+ decisionIds: [],
13317
+ reason: unavailable ? "required capability is unavailable" : gate.reason
13318
+ };
12863
13319
  if (!unavailable && related.length > 0 && !passed.length)
12864
13320
  return {
12865
13321
  requirementId: requirement.id,
@@ -13036,8 +13492,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
13036
13492
  revision_conflict means a revision you passed is stale: re-read the record
13037
13493
  before deciding whether to retry.
13038
13494
  A solo edit does not need writer acquisition; use it when concurrent checkout
13039
- writers need coordination. Record only observed facts and checks. Evidence can
13040
- become stale when its bound candidate changes; reconcile findings against the
13495
+ writers need coordination. Record only observed facts and checks. Close-time
13496
+ testing and verification gates accept only a configured check the CLI ran:
13497
+ \`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
13498
+ \`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
13499
+ \`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
13041
13500
  current candidate before recording completion.
13042
13501
 
13043
13502
  Workit validates domain policy against the actual action target, configured
@@ -13086,8 +13545,9 @@ target result; a local commit alone is not evidence of a requested remote push.
13086
13545
  Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
13087
13546
  substantial interruption or change of direction, workit-deslop when relevant
13088
13547
  to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
13089
- when impact is uncertain, and workit-challenge for genuinely open consequential
13090
- choices. Load workit-plan when dependencies or handoff need durable next actions.
13548
+ when impact is uncertain, workit-challenge for genuinely open consequential
13549
+ choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
13550
+ workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
13091
13551
  Load the skill; never act from memory of it.
13092
13552
  `.trim();
13093
13553
 
@@ -13098,7 +13558,7 @@ function reconcileResume(view, observations = []) {
13098
13558
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
13099
13559
  if (!candidate.ok)
13100
13560
  return candidate;
13101
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13561
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13102
13562
  const blockers = [...view.task.progress.blockers];
13103
13563
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
13104
13564
  blockers.push({
@@ -13996,10 +14456,7 @@ class WorkitCore {
13996
14456
  if (finding.data.disposition !== "fixed")
13997
14457
  return finding;
13998
14458
  const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
13999
- const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
14000
- item.evidenceId,
14001
- item.status
14002
- ]));
14459
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
14003
14460
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
14004
14461
  kind: item.data.kind,
14005
14462
  candidateId: item.data.candidateId,
@@ -14014,6 +14471,71 @@ class WorkitCore {
14014
14471
  return changed;
14015
14472
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
14016
14473
  }
14474
+ observeCheck(request) {
14475
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
14476
+ }
14477
+ observeCheckOnce(request) {
14478
+ const root = this.contextRootError();
14479
+ if (!root.ok)
14480
+ return root;
14481
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
14482
+ return failure2("permission_denied", "only the workit CLI records observed checks");
14483
+ const observation = checkObservationSchema.safeParse(request?.observation);
14484
+ if (!observation.success)
14485
+ return failure2("invalid_input", "check observation is invalid", {
14486
+ fields: observation.error.issues.map((issue) => ({
14487
+ path: issue.path.join("."),
14488
+ reason: issue.message
14489
+ }))
14490
+ });
14491
+ const task = this.store.readTask(request.taskId);
14492
+ if (!task.ok)
14493
+ return task;
14494
+ if (task.data.status === "closed")
14495
+ return failure2("invalid_transition", "closed task cannot record evidence");
14496
+ const observed = observation.data;
14497
+ let candidate = null;
14498
+ if (observed.tree === null) {
14499
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
14500
+ if (!captured.ok)
14501
+ return captured;
14502
+ candidate = captured.data;
14503
+ }
14504
+ const label = observed.name ?? observed.argv.join(" ");
14505
+ const data = {
14506
+ kind: "check",
14507
+ claim: `workit check ${label}`.slice(0, 500),
14508
+ requirementIds: [],
14509
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
14510
+ result: observed.exitCode === 0 ? "passed" : "failed",
14511
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
14512
+ refs: [],
14513
+ exitCode: observed.exitCode,
14514
+ reviewContext: null,
14515
+ observation: observed
14516
+ };
14517
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
14518
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
14519
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
14520
+ return success(mutation.revision, null, {
14521
+ ...current,
14522
+ critical,
14523
+ candidates,
14524
+ evidence: [
14525
+ ...current.evidence,
14526
+ {
14527
+ id: newId(),
14528
+ recordedAt: mutation.now,
14529
+ provenance: provenance(this.context, "host_observed"),
14530
+ data
14531
+ }
14532
+ ]
14533
+ });
14534
+ }, trustedNow(this.context));
14535
+ if (!changed.ok)
14536
+ return changed;
14537
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
14538
+ }
14017
14539
  decision(request) {
14018
14540
  return this.recordDecision(request);
14019
14541
  }
@@ -14249,7 +14771,7 @@ class WorkitCore {
14249
14771
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
14250
14772
  if (!current.ok)
14251
14773
  return current;
14252
- const evaluations = evaluateEvidence(task.data, current.data);
14774
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
14253
14775
  const verified = evidence.some((entry) => {
14254
14776
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
14255
14777
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -14953,7 +15475,8 @@ class WorkitCore {
14953
15475
  if (!current.ok)
14954
15476
  return current;
14955
15477
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14956
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15478
+ const tree = treeFreshness(this.store.root);
15479
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
14957
15480
  return success(task.revision, workspace.data.revision, {
14958
15481
  id: task.id,
14959
15482
  revision: task.revision,
@@ -14970,7 +15493,7 @@ class WorkitCore {
14970
15493
  writer: evaluationWorkspace.writer
14971
15494
  });
14972
15495
  }
14973
- compactContext(taskId) {
15496
+ compactContext(taskId, freshness) {
14974
15497
  const root = this.contextRootError();
14975
15498
  if (!root.ok)
14976
15499
  return root;
@@ -14980,12 +15503,12 @@ class WorkitCore {
14980
15503
  const helper = this.helperTaskGuard(task.data, true);
14981
15504
  if (!helper.ok)
14982
15505
  return helper;
14983
- const view = this.view(task.data, false);
15506
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
14984
15507
  if (!view.ok)
14985
15508
  return view;
14986
15509
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
14987
15510
  }
14988
- view(task, capture = true) {
15511
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
14989
15512
  const workspace = this.store.readWorkspace();
14990
15513
  if (!workspace.ok)
14991
15514
  return workspace;
@@ -14996,13 +15519,14 @@ class WorkitCore {
14996
15519
  if (!current.ok)
14997
15520
  return current;
14998
15521
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14999
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15522
+ const tree = freshness;
15523
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
15000
15524
  return success(task.revision, workspace.data.revision, {
15001
15525
  task,
15002
15526
  workspace: evaluationWorkspace,
15003
15527
  capabilities: this.context.capabilities,
15004
15528
  requirements,
15005
- evidence: evaluateEvidence(task, current.data)
15529
+ evidence: evaluateEvidence(task, current.data, tree)
15006
15530
  });
15007
15531
  }
15008
15532
  reconcileResume(view, observations = []) {
@@ -15229,13 +15753,14 @@ class WorkitCore {
15229
15753
  return current;
15230
15754
  const withCandidate = current.data;
15231
15755
  const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
15232
- const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller);
15756
+ const tree = treeFreshness(this.store.root);
15757
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
15233
15758
  const view = {
15234
15759
  task: taskForView,
15235
15760
  workspace: workspace.data,
15236
15761
  capabilities: this.context.capabilities,
15237
15762
  requirements,
15238
- evidence: evaluateEvidence(taskForView, withCandidate)
15763
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
15239
15764
  };
15240
15765
  const closure = evaluateClosure(input.outcome, view);
15241
15766
  if (!closure.ok)
@@ -15289,7 +15814,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
15289
15814
  };
15290
15815
  });
15291
15816
  // packages/workit-core/src/hooks/context.ts
15292
- import path11 from "node:path";
15817
+ import path13 from "node:path";
15293
15818
  var boundTo = (entry, session) => entry.sessions.some((item) => item.host === session.host && item.handle === session.handle);
15294
15819
  var newestFirst = (left, right) => right.updatedAt.localeCompare(left.updatedAt);
15295
15820
  var sessionTaskEntry = (entries, session) => entries.filter((entry) => entry.status !== "closed" && boundTo(entry, session)).toSorted(newestFirst)[0] ?? null;
@@ -15314,6 +15839,19 @@ var filesKey = (files) => {
15314
15839
  return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
15315
15840
  };
15316
15841
  var CACHE_LIMIT = 64;
15842
+ var SIGNAL_TTL_MS = 1500;
15843
+ var signals = new Map;
15844
+ var recentSignal = (root) => {
15845
+ const now = Date.now();
15846
+ const cached = signals.get(root);
15847
+ if (cached && now - cached.at < SIGNAL_TTL_MS)
15848
+ return cached.value;
15849
+ const value = worktreeSignal(root);
15850
+ signals.set(root, { at: now, value });
15851
+ if (signals.size > CACHE_LIMIT)
15852
+ signals.delete(signals.keys().next().value);
15853
+ return value;
15854
+ };
15317
15855
  var cache = new Map;
15318
15856
  function sessionCompactContext(store, session, context, selection = "session-bound") {
15319
15857
  const listed = store.listTaskIndex();
@@ -15337,18 +15875,26 @@ function entryCompactContext(store, entry, session, context) {
15337
15875
  capabilities: context.capabilities
15338
15876
  });
15339
15877
  const hit = cache.get(slot);
15340
- if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
15878
+ if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
15341
15879
  return hit.value;
15342
15880
  const task = store.readTask(entry.id);
15343
15881
  if (!task.ok)
15344
15882
  return null;
15345
- const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path11.resolve(store.root, ref.path)] : []));
15346
- const observed = filesKey(files);
15347
- const compact = new WorkitCore(store, context).compactContext(entry.id);
15883
+ const freshness = { mode: "signal", current: () => recentSignal(store.root) };
15884
+ const observed = task.data.evidence.some(isObservedCheck);
15885
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path13.resolve(store.root, ref.path)] : []));
15886
+ const observedFiles = filesKey(files);
15887
+ const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
15348
15888
  if (!compact.ok)
15349
15889
  return null;
15350
15890
  cache.delete(slot);
15351
- cache.set(slot, { key, files, filesKey: observed, value: compact.data });
15891
+ cache.set(slot, {
15892
+ key,
15893
+ files,
15894
+ filesKey: observedFiles,
15895
+ signal: observed ? freshness.current() : null,
15896
+ value: compact.data
15897
+ });
15352
15898
  if (cache.size > CACHE_LIMIT)
15353
15899
  cache.delete(cache.keys().next().value);
15354
15900
  return compact.data;
@@ -15493,12 +16039,12 @@ var dispatchHook = (adapter, raw, env = process.env) => {
15493
16039
  };
15494
16040
  // packages/workit-core/src/hooks/hosts/fields.ts
15495
16041
  import { existsSync as existsSync5, realpathSync as realpathSync7, statSync as statSync2 } from "node:fs";
15496
- import path12 from "node:path";
15497
- var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
16042
+ import path14 from "node:path";
16043
+ var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
15498
16044
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
15499
16045
  var optionalText = (value) => typeof value === "string" ? value : null;
15500
16046
  var existingDirectory = (value) => {
15501
- if (!nonEmpty2(value) || !path12.isAbsolute(value) || !existsSync5(value))
16047
+ if (!nonEmpty2(value) || !path14.isAbsolute(value) || !existsSync5(value))
15502
16048
  return null;
15503
16049
  try {
15504
16050
  return statSync2(value).isDirectory() ? realpathSync7(value) : null;
@@ -15635,7 +16181,7 @@ var EVENTS = {
15635
16181
  };
15636
16182
  var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
15637
16183
  var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
15638
- var toolCommand = (value) => isRecord2(value.tool_input) ? commandText(value.tool_input.command) : null;
16184
+ var toolCommand = (value) => isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
15639
16185
  var eventOf = (name, value) => {
15640
16186
  const toolUseId = optionalText(value.tool_use_id);
15641
16187
  switch (name) {
@@ -15655,7 +16201,7 @@ var eventOf = (name, value) => {
15655
16201
  const command = toolCommand(value);
15656
16202
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
15657
16203
  return { ok: false, error: "only shell PostToolUse is mapped" };
15658
- const response = isRecord2(value.tool_response) ? value.tool_response : {};
16204
+ const response = isRecord3(value.tool_response) ? value.tool_response : {};
15659
16205
  return {
15660
16206
  ok: true,
15661
16207
  event: {
@@ -15731,14 +16277,14 @@ var render = (decision, native) => {
15731
16277
  var claudeCodeAdapter = {
15732
16278
  descriptor: CLAUDE_CODE_DESCRIPTOR,
15733
16279
  parse(raw) {
15734
- const native = isRecord2(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16280
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15735
16281
  const fail = (error) => ({
15736
16282
  ok: false,
15737
16283
  error,
15738
16284
  native,
15739
16285
  event: native ? EVENTS[native] : null
15740
16286
  });
15741
- if (!isRecord2(raw) || !native)
16287
+ if (!isRecord3(raw) || !native)
15742
16288
  return fail("hook_event_name is required");
15743
16289
  if (!nonEmpty2(raw.session_id))
15744
16290
  return fail("session_id is required");
@@ -15887,7 +16433,7 @@ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
15887
16433
  var isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase());
15888
16434
  var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
15889
16435
  var parseCodexHookInput = (value) => {
15890
- if (!isRecord2(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
16436
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
15891
16437
  return { ok: false, error: "hook_event_name is required" };
15892
16438
  const event = value.hook_event_name;
15893
16439
  const cwd = existingDirectory(value.cwd);
@@ -15895,7 +16441,7 @@ var parseCodexHookInput = (value) => {
15895
16441
  return { ok: false, error: "cwd must be an existing absolute directory" };
15896
16442
  if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
15897
16443
  return { ok: false, error: "tool_name is required" };
15898
- const command = isRecord2(value.tool_input) ? commandText(value.tool_input.command) : null;
16444
+ const command = isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
15899
16445
  if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
15900
16446
  return { ok: false, error: "tool_input.command is required for shell tools" };
15901
16447
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -15978,7 +16524,7 @@ var codexAdapter = {
15978
16524
  const host = detectCodexSurface(env);
15979
16525
  const parsed = parseCodexHookInput(raw);
15980
16526
  if (!parsed.ok) {
15981
- const native = isRecord2(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16527
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15982
16528
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
15983
16529
  }
15984
16530
  const input = parsed.data;
@@ -16005,7 +16551,7 @@ var codexAdapter = {
16005
16551
  };
16006
16552
 
16007
16553
  // packages/workit-core/src/hooks/hosts/cursor.ts
16008
- import path13 from "node:path";
16554
+ import path15 from "node:path";
16009
16555
  var undocumented2 = { support: "undocumented", native: null };
16010
16556
  var none = { support: "none", native: null };
16011
16557
  var CURSOR_DESCRIPTOR = {
@@ -16124,7 +16670,7 @@ var EVENTS3 = {
16124
16670
  preCompact: "compact.pre"
16125
16671
  };
16126
16672
  var parseCursorHookInput = (value) => {
16127
- if (!isRecord2(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16673
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16128
16674
  return { ok: false, error: "hook_event_name is required" };
16129
16675
  const roots = value.workspace_roots;
16130
16676
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -16188,7 +16734,7 @@ var protocolEvent2 = (input) => {
16188
16734
  return { kind: "compact.pre", trigger: "auto" };
16189
16735
  }
16190
16736
  };
16191
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path13.sep) ? root : `${root}${path13.sep}`);
16737
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path15.sep) ? root : `${root}${path15.sep}`);
16192
16738
  var hookCwd = (input) => {
16193
16739
  const cwd = existingDirectory(input.cwd);
16194
16740
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -16225,7 +16771,7 @@ var cursorAdapter = {
16225
16771
  parse(raw) {
16226
16772
  const parsed = parseCursorHookInput(raw);
16227
16773
  if (!parsed.ok) {
16228
- const native = isRecord2(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16774
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16229
16775
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
16230
16776
  }
16231
16777
  const input = parsed.data;