@brainervirus/workit-cursor 2.6.0 → 2.7.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.
@@ -7825,7 +7825,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
7825
7825
  }, rewriteRecordRefs = (value, schema, map) => {
7826
7826
  const rewritten = rewriteNode(value, schema, map);
7827
7827
  return rewritten.drop ? null : rewritten.value;
7828
- }, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0), strippable = (issues, prefix = []) => {
7828
+ }, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, checkObservationSchema, CHECK_OBSERVATION_PATH = "evidence.*.data.observation", evidenceFields, evidenceSchema, reportedEvidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0), strippable = (issues, prefix = []) => {
7829
7829
  const strips = [];
7830
7830
  for (const issue of issues) {
7831
7831
  if (issue.code === "unrecognized_keys") {
@@ -8157,7 +8157,36 @@ var init_task_contract = __esm(() => {
8157
8157
  path: ["id"]
8158
8158
  });
8159
8159
  });
8160
- evidenceSchema = object({
8160
+ checkObservationSchema = object({
8161
+ observer: literal("workit_cli"),
8162
+ name: nonEmpty.nullable(),
8163
+ configured: boolean2(),
8164
+ argv: array(text).min(1),
8165
+ shell: boolean2(),
8166
+ cwd: nonEmpty,
8167
+ exitCode: safeInteger,
8168
+ durationMs: safeInteger.min(0),
8169
+ timedOut: boolean2(),
8170
+ head: text.nullable(),
8171
+ tree: text.nullable(),
8172
+ dirty: boolean2().nullable(),
8173
+ signal: text.nullable(),
8174
+ treeAfter: text.nullable(),
8175
+ modifiedWorktree: boolean2(),
8176
+ base: text.nullable(),
8177
+ patchId: text.nullable(),
8178
+ environment: object({
8179
+ platform: nonEmpty,
8180
+ arch: nonEmpty,
8181
+ vars: record(nonEmpty, text.nullable())
8182
+ }).strict(),
8183
+ logDigest: text.nullable(),
8184
+ logRef: text.nullable(),
8185
+ logTail: array(text).max(40),
8186
+ ledgerRowId: text.nullable(),
8187
+ attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
8188
+ }).strict();
8189
+ evidenceFields = {
8161
8190
  kind: _enum(["check", "review", "investigation", "artifact"]),
8162
8191
  claim: text,
8163
8192
  requirementIds: array(digest),
@@ -8168,7 +8197,9 @@ var init_task_contract = __esm(() => {
8168
8197
  refs: array(refSchema),
8169
8198
  exitCode: safeInteger.nullable(),
8170
8199
  reviewContext: refSchema.nullable()
8171
- }).strict();
8200
+ };
8201
+ evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
8202
+ reportedEvidenceSchema = object(evidenceFields).strict();
8172
8203
  evidenceEvaluationSchema = object({
8173
8204
  evidenceId: id,
8174
8205
  status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
@@ -8425,7 +8456,7 @@ var init_task_contract = __esm(() => {
8425
8456
  action: literal("record"),
8426
8457
  ...taskId,
8427
8458
  expectedRevision: revision.optional(),
8428
- evidence: evidenceSchema
8459
+ evidence: reportedEvidenceSchema
8429
8460
  })
8430
8461
  };
8431
8462
  findingOperations = {
@@ -12500,15 +12531,340 @@ var init_policy_resolver = __esm(() => {
12500
12531
  }).strict();
12501
12532
  });
12502
12533
 
12503
- // packages/workit-core/src/core/task-evaluation.ts
12504
- import { createHash as createHash7 } from "node:crypto";
12505
- import * as fs8 from "node:fs";
12534
+ // packages/workit-core/src/check-config.ts
12506
12535
  import { spawnSync as spawnSync3 } from "node:child_process";
12536
+ import fs8 from "node:fs";
12507
12537
  import path10 from "node:path";
12538
+ function splitCommand(command) {
12539
+ const argv = [];
12540
+ let current = "";
12541
+ let quote = null;
12542
+ let started = false;
12543
+ for (const char of command) {
12544
+ if (quote) {
12545
+ if (char === quote)
12546
+ quote = null;
12547
+ else
12548
+ current += char;
12549
+ continue;
12550
+ }
12551
+ if (char === '"' || char === "'") {
12552
+ quote = char;
12553
+ started = true;
12554
+ continue;
12555
+ }
12556
+ if (/\s/u.test(char)) {
12557
+ if (started)
12558
+ argv.push(current);
12559
+ current = "";
12560
+ started = false;
12561
+ continue;
12562
+ }
12563
+ if (SHELL_META.test(char))
12564
+ return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
12565
+ current += char;
12566
+ started = true;
12567
+ }
12568
+ if (quote)
12569
+ return new Error(`"${command}" has an unterminated quote`);
12570
+ if (started)
12571
+ argv.push(current);
12572
+ return argv.length ? argv : new Error("empty command");
12573
+ }
12574
+ function checkRoot(cwd) {
12575
+ return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
12576
+ }
12577
+ function detectedChecks(root) {
12578
+ const read = (file) => {
12579
+ try {
12580
+ return fs8.readFileSync(path10.join(root, file), "utf8");
12581
+ } catch {
12582
+ return null;
12583
+ }
12584
+ };
12585
+ const checks = [];
12586
+ const add = (check) => {
12587
+ if (!checks.some((item) => item.name === check.name))
12588
+ checks.push(check);
12589
+ };
12590
+ const manifestText = read("package.json");
12591
+ if (manifestText !== null) {
12592
+ let manifest = null;
12593
+ try {
12594
+ manifest = JSON.parse(manifestText);
12595
+ } catch {}
12596
+ const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
12597
+ const pm = packageManager(root);
12598
+ for (const name of SCRIPT_CHECKS) {
12599
+ const script = scripts[name];
12600
+ if (typeof script === "string" && script.trim() !== "")
12601
+ add(scriptCheck(pm, name));
12602
+ }
12603
+ }
12604
+ const plain = (name, argv) => ({ name, argv, accepts: [argv] });
12605
+ if (read("go.mod") !== null)
12606
+ add(plain("test", ["go", "test", "./..."]));
12607
+ if (read("Cargo.toml") !== null)
12608
+ add(plain("test", ["cargo", "test"]));
12609
+ if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
12610
+ add(plain("test", ["pytest"]));
12611
+ const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
12612
+ if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
12613
+ add(plain("test", ["make", "test"]));
12614
+ return checks;
12615
+ }
12616
+ function loadCheckConfig(cwd) {
12617
+ const root = checkRoot(cwd);
12618
+ const file = path10.join(root, CHECKS_FILE);
12619
+ const none = (error = null) => ({
12620
+ source: error ? CHECKS_FILE : "none",
12621
+ root,
12622
+ checks: [],
12623
+ gates: {},
12624
+ error
12625
+ });
12626
+ let text = null;
12627
+ try {
12628
+ text = fs8.readFileSync(file, "utf8");
12629
+ } catch (error) {
12630
+ if (error.code !== "ENOENT")
12631
+ return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
12632
+ }
12633
+ if (text === null) {
12634
+ const checks = detectedChecks(root);
12635
+ return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
12636
+ }
12637
+ let parsed;
12638
+ try {
12639
+ parsed = JSON.parse(text);
12640
+ } catch {
12641
+ return none(`${CHECKS_FILE} is not valid JSON`);
12642
+ }
12643
+ if (!isRecord2(parsed) || !isRecord2(parsed.checks))
12644
+ return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
12645
+ const checks = [];
12646
+ for (const [name, value] of Object.entries(parsed.checks)) {
12647
+ if (!CHECK_NAME.test(name))
12648
+ return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
12649
+ 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");
12650
+ if (argv instanceof Error)
12651
+ return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
12652
+ checks.push({ name, argv, accepts: [argv] });
12653
+ }
12654
+ const gates = {};
12655
+ if (parsed.gates !== undefined) {
12656
+ if (!isRecord2(parsed.gates))
12657
+ return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
12658
+ for (const [gate, value] of Object.entries(parsed.gates)) {
12659
+ if (!GATES.includes(gate))
12660
+ return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
12661
+ const names = typeof value === "string" ? [value] : value;
12662
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
12663
+ return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
12664
+ gates[gate] = names;
12665
+ }
12666
+ }
12667
+ return { source: CHECKS_FILE, root, checks, gates, error: null };
12668
+ }
12669
+ function matchesNamedCheck(config, name, argv, cwd) {
12670
+ if (config.error || name === null || cwd !== ".")
12671
+ return false;
12672
+ const check = config.checks.find((item) => item.name === name);
12673
+ return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
12674
+ }
12675
+ function gateCheckNames(config, dimension) {
12676
+ const explicit = config.gates[dimension];
12677
+ if (explicit)
12678
+ return explicit;
12679
+ const names = config.checks.map((check) => check.name);
12680
+ if (dimension === "testing")
12681
+ return names.includes("test") ? ["test"] : [];
12682
+ return names;
12683
+ }
12684
+ function workitAvailable(env = process.env) {
12685
+ if (workitOnPath !== undefined)
12686
+ return workitOnPath;
12687
+ const windows = process.platform === "win32";
12688
+ const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
12689
+ const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
12690
+ workitOnPath = dirs.some((dir) => exts.some((ext) => {
12691
+ try {
12692
+ return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
12693
+ } catch {
12694
+ return false;
12695
+ }
12696
+ }));
12697
+ return workitOnPath;
12698
+ }
12699
+ function checkCommand(args, version, onPath = workitAvailable()) {
12700
+ return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
12701
+ }
12702
+ var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git = (cwd, args) => {
12703
+ const run = spawnSync3("git", args, {
12704
+ cwd,
12705
+ encoding: "utf8",
12706
+ timeout: 20000,
12707
+ killSignal: "SIGKILL",
12708
+ windowsHide: true
12709
+ });
12710
+ const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
12711
+ return value || null;
12712
+ }, packageManager = (root) => {
12713
+ const has = (file) => fs8.existsSync(path10.join(root, file));
12714
+ if (has("bun.lock") || has("bun.lockb"))
12715
+ return "bun";
12716
+ if (has("pnpm-lock.yaml"))
12717
+ return "pnpm";
12718
+ if (has("yarn.lock"))
12719
+ return "yarn";
12720
+ return "npm";
12721
+ }, scriptCheck = (pm, name) => {
12722
+ const argv = [pm, "run", name];
12723
+ const accepts = [argv];
12724
+ if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
12725
+ accepts.push([pm, name]);
12726
+ return { name, argv, accepts };
12727
+ }, CHECK_NAME, GATES, sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]), workitOnPath;
12728
+ var init_check_config = __esm(() => {
12729
+ SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
12730
+ SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
12731
+ CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
12732
+ GATES = ["testing", "verification"];
12733
+ });
12734
+
12735
+ // packages/workit-core/src/git/rev.ts
12736
+ import { spawnSync as spawnSync4 } from "node:child_process";
12737
+ import { createHash as createHash7 } from "node:crypto";
12738
+ import fs9 from "node:fs";
12739
+ import os2 from "node:os";
12740
+ import path11 from "node:path";
12741
+ function headSha(cwd) {
12742
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
12743
+ }
12744
+ function worktreeTree(cwd, options = {}) {
12745
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
12746
+ const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
12747
+ if (!top || !indexPath)
12748
+ return null;
12749
+ const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
12750
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
12751
+ const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
12752
+ try {
12753
+ const tempIndex = path11.join(scratch, "index");
12754
+ if (fs9.existsSync(indexPath))
12755
+ seedIndex(indexPath, tempIndex);
12756
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
12757
+ const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
12758
+ env,
12759
+ timeoutMs
12760
+ });
12761
+ if (!untracked.ok)
12762
+ return null;
12763
+ const skipped = [];
12764
+ for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
12765
+ try {
12766
+ const stat = fs9.statSync(path11.join(top, file));
12767
+ if (stat.isFile() && stat.size > cap)
12768
+ skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
12769
+ } catch {}
12770
+ }
12771
+ skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
12772
+ const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
12773
+ env,
12774
+ timeoutMs,
12775
+ input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
12776
+ }) : git2(top, ["add", "-A"], { env, timeoutMs });
12777
+ if (!added.ok)
12778
+ return null;
12779
+ const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
12780
+ if (!tree)
12781
+ return null;
12782
+ const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
12783
+ const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
12784
+ return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
12785
+ } finally {
12786
+ fs9.rmSync(scratch, { recursive: true, force: true });
12787
+ }
12788
+ }
12789
+ function worktreeSignal(cwd, options = {}) {
12790
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
12791
+ if (!top)
12792
+ return null;
12793
+ const status = git2(top, [
12794
+ "status",
12795
+ "--porcelain=v1",
12796
+ "-z",
12797
+ "--untracked-files=all",
12798
+ "--no-renames",
12799
+ "--ignore-submodules=dirty"
12800
+ ], {
12801
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
12802
+ timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
12803
+ });
12804
+ if (!status.ok)
12805
+ return null;
12806
+ const hash = createHash7("sha256");
12807
+ hash.update(`${headSha(top) ?? "unborn"}\x00`);
12808
+ for (const entry of status.stdout.split("\x00")) {
12809
+ if (entry.length < 4)
12810
+ continue;
12811
+ const file = entry.slice(3);
12812
+ const absolute = path11.join(top, file);
12813
+ let stat = "-";
12814
+ try {
12815
+ const info = fs9.lstatSync(absolute, { bigint: true });
12816
+ stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
12817
+ if (info.isDirectory())
12818
+ stat += `:${headSha(absolute) ?? "-"}`;
12819
+ } catch {}
12820
+ hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
12821
+ }
12822
+ return `sig:${hash.digest("hex")}`;
12823
+ }
12824
+ var MAX_BUFFER, GIT_TIMEOUTS, git2 = (cwd, args, options = {}) => {
12825
+ const result = spawnSync4("git", args, {
12826
+ cwd,
12827
+ encoding: "utf8",
12828
+ env: options.env ?? process.env,
12829
+ input: options.input,
12830
+ maxBuffer: MAX_BUFFER,
12831
+ timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
12832
+ killSignal: "SIGKILL",
12833
+ windowsHide: true
12834
+ });
12835
+ const error = result.error;
12836
+ return {
12837
+ ok: result.status === 0,
12838
+ stdout: result.stdout ?? "",
12839
+ stderr: result.stderr ?? "",
12840
+ timedOut: error?.code === "ETIMEDOUT"
12841
+ };
12842
+ }, line = (run) => {
12843
+ if (!run.ok)
12844
+ return null;
12845
+ const value = run.stdout.trim();
12846
+ return value ? value : null;
12847
+ }, emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" })), MAX_UNTRACKED_BYTES, seedIndex = (from, to) => {
12848
+ fs9.copyFileSync(from, to);
12849
+ const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
12850
+ const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
12851
+ fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
12852
+ };
12853
+ var init_rev = __esm(() => {
12854
+ MAX_BUFFER = 256 * 1024 * 1024;
12855
+ GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
12856
+ MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
12857
+ });
12858
+
12859
+ // packages/workit-core/src/core/task-evaluation.ts
12860
+ import { createHash as createHash8 } from "node:crypto";
12861
+ import * as fs10 from "node:fs";
12862
+ import { spawnSync as spawnSync5 } from "node:child_process";
12863
+ import path12 from "node:path";
12508
12864
  function captureCandidate(root, scope, environment = []) {
12509
12865
  let checkout;
12510
12866
  try {
12511
- checkout = fs8.realpathSync(root);
12867
+ checkout = fs10.realpathSync(root);
12512
12868
  } catch {
12513
12869
  return failure2("invalid_input", "candidate root does not exist");
12514
12870
  }
@@ -12526,11 +12882,11 @@ function captureCandidate(root, scope, environment = []) {
12526
12882
  if (item === ".git" || item === ".workit")
12527
12883
  continue;
12528
12884
  names.add(item);
12529
- if (!fs8.existsSync(target))
12885
+ if (!fs10.existsSync(target))
12530
12886
  continue;
12531
12887
  let stat;
12532
12888
  try {
12533
- stat = fs8.lstatSync(target);
12889
+ stat = fs10.lstatSync(target);
12534
12890
  } catch {
12535
12891
  uncertain = true;
12536
12892
  continue;
@@ -12546,17 +12902,17 @@ function captureCandidate(root, scope, environment = []) {
12546
12902
  for (const item of [...names].toSorted()) {
12547
12903
  if (!scopeMatches(item, normalizedScope))
12548
12904
  continue;
12549
- const target = path10.join(checkout, item);
12905
+ const target = path12.join(checkout, item);
12550
12906
  try {
12551
- const stat = fs8.lstatSync(target);
12907
+ const stat = fs10.lstatSync(target);
12552
12908
  if (stat.isSymbolicLink()) {
12553
- const link = fs8.readlinkSync(target);
12909
+ const link = fs10.readlinkSync(target);
12554
12910
  files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
12555
12911
  } else if (stat.isFile()) {
12556
12912
  files.push({
12557
12913
  path: item,
12558
12914
  kind: "file",
12559
- digest: digestBytes3(fs8.readFileSync(target)),
12915
+ digest: digestBytes3(fs10.readFileSync(target)),
12560
12916
  executable: (stat.mode & 73) !== 0
12561
12917
  });
12562
12918
  } else if (!stat.isDirectory()) {
@@ -12581,7 +12937,7 @@ function captureCandidate(root, scope, environment = []) {
12581
12937
  const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
12582
12938
  if (values.some(({ value }) => value === null))
12583
12939
  completeness = "uncertain";
12584
- const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
12940
+ const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
12585
12941
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
12586
12942
  completeness = "uncertain";
12587
12943
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -12597,7 +12953,7 @@ function captureCandidate(root, scope, environment = []) {
12597
12953
  const parsed = candidateSchema.safeParse(candidate);
12598
12954
  return parsed.success ? success(null, null, parsed.data) : failure2("invalid_input", "captured candidate is invalid");
12599
12955
  }
12600
- function evaluateEvidence(task, candidate) {
12956
+ function evaluateEvidence(task, candidate, freshness) {
12601
12957
  const current = candidate ?? task.candidates.at(-1) ?? null;
12602
12958
  return task.evidence.map((entry) => {
12603
12959
  const evidence = entry.data;
@@ -12607,6 +12963,25 @@ function evaluateEvidence(task, candidate) {
12607
12963
  status: "failed",
12608
12964
  reason: "failed evidence remains historical"
12609
12965
  };
12966
+ const observation = isObservedCheck(entry) ? evidence.observation : undefined;
12967
+ if (observation && observation.tree !== null) {
12968
+ if (task.status === "closed")
12969
+ return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
12970
+ if (observation.modifiedWorktree)
12971
+ return {
12972
+ evidenceId: entry.id,
12973
+ status: "stale",
12974
+ reason: "the check changed the worktree while it ran; ignore or clean its outputs"
12975
+ };
12976
+ const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
12977
+ if (recorded !== null && freshness && recorded === freshness.current())
12978
+ return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
12979
+ return {
12980
+ evidenceId: entry.id,
12981
+ status: "stale",
12982
+ 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"
12983
+ };
12984
+ }
12610
12985
  if (evidence.kind === "check" || evidence.kind === "review") {
12611
12986
  if (!evidence.beforeCandidateId || !evidence.candidateId)
12612
12987
  return {
@@ -12644,8 +13019,44 @@ function evaluateEvidence(task, candidate) {
12644
13019
  };
12645
13020
  });
12646
13021
  }
12647
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
12648
- const evidence = evaluateEvidence(task, candidate);
13022
+ function evaluateObservedGate(task, requirement, statuses, config) {
13023
+ const names = gateCheckNames(config, requirement.dimension);
13024
+ const observed = task.evidence.filter(isObservedCheck);
13025
+ const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
13026
+ const named = (entry) => {
13027
+ const observation = entry.data.observation;
13028
+ return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
13029
+ };
13030
+ const considered = observed.map((entry) => entry.id);
13031
+ const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
13032
+ if (config.error)
13033
+ return {
13034
+ satisfied: [],
13035
+ considered,
13036
+ reason: `check config is invalid (${config.error}); fix it, then run ${run}`
13037
+ };
13038
+ if (!names.length)
13039
+ return {
13040
+ satisfied: [],
13041
+ considered,
13042
+ 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())}`
13043
+ };
13044
+ const satisfied = fresh.filter(named);
13045
+ if (satisfied.length)
13046
+ return {
13047
+ satisfied: satisfied.map((entry) => entry.id),
13048
+ considered,
13049
+ reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
13050
+ };
13051
+ const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
13052
+ 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}`;
13053
+ return { satisfied: [], considered, reason };
13054
+ }
13055
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
13056
+ const evidence = evaluateEvidence(task, candidate, freshness);
13057
+ const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
13058
+ let checkConfig;
13059
+ const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
12649
13060
  if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
12650
13061
  return task.policy.requirements.map((requirement) => ({
12651
13062
  requirementId: requirement.id,
@@ -12655,8 +13066,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
12655
13066
  reason: "stored policy version is unsupported; reassessment is required"
12656
13067
  }));
12657
13068
  return (task.policy?.requirements ?? []).map((requirement) => {
13069
+ const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
13070
+ if (gate?.satisfied.length)
13071
+ return {
13072
+ requirementId: requirement.id,
13073
+ status: "satisfied",
13074
+ evidenceIds: gate.satisfied,
13075
+ decisionIds: [],
13076
+ reason: gate.reason
13077
+ };
12658
13078
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
12659
13079
  const passed = related.filter(({ entry, evaluation }) => {
13080
+ if (gate)
13081
+ return false;
12660
13082
  if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
12661
13083
  return false;
12662
13084
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
@@ -12719,6 +13141,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
12719
13141
  reason: "an applicable approved limitation permits the missing evidence"
12720
13142
  };
12721
13143
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
13144
+ if (gate)
13145
+ return {
13146
+ requirementId: requirement.id,
13147
+ status: unavailable ? "unavailable" : "unsatisfied",
13148
+ evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
13149
+ decisionIds: [],
13150
+ reason: unavailable ? "required capability is unavailable" : gate.reason
13151
+ };
12722
13152
  if (!unavailable && related.length > 0 && !passed.length)
12723
13153
  return {
12724
13154
  requirementId: requirement.id,
@@ -12817,12 +13247,12 @@ function evaluateClosure(requestedOutcome, view) {
12817
13247
  requirementIds: evaluations.map((item) => item.requirementId)
12818
13248
  });
12819
13249
  }
12820
- var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`), relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".", canonicalPath = (value) => {
13250
+ var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`), relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".", canonicalPath = (value) => {
12821
13251
  if (typeof value !== "string" || !value)
12822
13252
  return null;
12823
13253
  if (value.includes("\\") || value.split("/").includes(".."))
12824
13254
  return null;
12825
- const normalized = path10.posix.normalize(value);
13255
+ const normalized = path12.posix.normalize(value);
12826
13256
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
12827
13257
  return null;
12828
13258
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -12852,7 +13282,7 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12852
13282
  }
12853
13283
  return left.length - right.length;
12854
13284
  }, gitPaths = (root) => {
12855
- const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
13285
+ const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
12856
13286
  cwd: root,
12857
13287
  encoding: "buffer"
12858
13288
  });
@@ -12864,20 +13294,20 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12864
13294
  git: !/not a git repository/i.test(stderr)
12865
13295
  };
12866
13296
  }
12867
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
12868
- const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
13297
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
13298
+ const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
12869
13299
  if (stagedDeleted.status !== 0) {
12870
13300
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
12871
13301
  if (!/not a git repository/i.test(stderr))
12872
13302
  return { paths, uncertain: true, git: true };
12873
13303
  } else if (stagedDeleted.stdout) {
12874
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
13304
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
12875
13305
  }
12876
13306
  return { paths: [...new Set(paths)], uncertain: false, git: true };
12877
13307
  }, walk = (root, directory, output) => {
12878
13308
  let names;
12879
13309
  try {
12880
- names = fs8.readdirSync(directory);
13310
+ names = fs10.readdirSync(directory);
12881
13311
  } catch {
12882
13312
  return true;
12883
13313
  }
@@ -12885,10 +13315,10 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12885
13315
  for (const name of names) {
12886
13316
  if (name === ".git" || name === ".workit")
12887
13317
  continue;
12888
- const target = path10.join(directory, name);
13318
+ const target = path12.join(directory, name);
12889
13319
  let stat;
12890
13320
  try {
12891
- stat = fs8.lstatSync(target);
13321
+ stat = fs10.lstatSync(target);
12892
13322
  } catch {
12893
13323
  uncertain = true;
12894
13324
  continue;
@@ -12902,14 +13332,14 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12902
13332
  }, scopeRoots = (root, scope) => {
12903
13333
  const roots = [];
12904
13334
  for (const item of scope.paths.length ? scope.paths : ["."]) {
12905
- const target = path10.resolve(root, item);
13335
+ const target = path12.resolve(root, item);
12906
13336
  if (!inside(root, target))
12907
13337
  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` }] });
12908
13338
  let ancestor = target;
12909
- while (!fs8.existsSync(ancestor) && ancestor !== root)
12910
- ancestor = path10.dirname(ancestor);
13339
+ while (!fs10.existsSync(ancestor) && ancestor !== root)
13340
+ ancestor = path12.dirname(ancestor);
12911
13341
  try {
12912
- if (!inside(root, fs8.realpathSync(ancestor)))
13342
+ if (!inside(root, fs10.realpathSync(ancestor)))
12913
13343
  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` }] });
12914
13344
  } catch {
12915
13345
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -12934,7 +13364,20 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12934
13364
  }, sameSession = (left, right) => {
12935
13365
  const other = sessionFromRef(right);
12936
13366
  return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
12937
- }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
13367
+ }, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), memo2 = (compute) => {
13368
+ let value;
13369
+ return () => {
13370
+ if (value === undefined)
13371
+ value = compute();
13372
+ return value;
13373
+ };
13374
+ }, treeFreshness = (root) => ({
13375
+ mode: "tree",
13376
+ current: memo2(() => worktreeTree(root)?.key ?? null)
13377
+ }), signalFreshness = (root) => ({
13378
+ mode: "signal",
13379
+ current: memo2(() => worktreeSignal(root))
13380
+ }), isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli", scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
12938
13381
  const pin = assignment.candidateId ?? null;
12939
13382
  if (pin === null)
12940
13383
  return true;
@@ -12972,10 +13415,13 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
12972
13415
  if (dimension === "decisions")
12973
13416
  return "decision";
12974
13417
  return "worker report";
12975
- };
13418
+ }, observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
12976
13419
  var init_task_evaluation = __esm(() => {
12977
13420
  init_task_contract();
12978
13421
  init_authority();
13422
+ init_check_config();
13423
+ init_rev();
13424
+ init_task_store();
12979
13425
  scopeCovers2 = scopeCovers;
12980
13426
  });
12981
13427
 
@@ -13044,8 +13490,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
13044
13490
  revision_conflict means a revision you passed is stale: re-read the record
13045
13491
  before deciding whether to retry.
13046
13492
  A solo edit does not need writer acquisition; use it when concurrent checkout
13047
- writers need coordination. Record only observed facts and checks. Evidence can
13048
- become stale when its bound candidate changes; reconcile findings against the
13493
+ writers need coordination. Record only observed facts and checks. Close-time
13494
+ testing and verification gates accept only a configured check the CLI ran:
13495
+ \`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
13496
+ \`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
13497
+ \`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
13049
13498
  current candidate before recording completion.
13050
13499
 
13051
13500
  Workit validates domain policy against the actual action target, configured
@@ -13118,7 +13567,7 @@ function reconcileResume(view, observations = []) {
13118
13567
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
13119
13568
  if (!candidate.ok)
13120
13569
  return candidate;
13121
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13570
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13122
13571
  const blockers = [...view.task.progress.blockers];
13123
13572
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
13124
13573
  blockers.push({
@@ -13608,10 +14057,7 @@ class WorkitCore {
13608
14057
  if (finding.data.disposition !== "fixed")
13609
14058
  return finding;
13610
14059
  const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
13611
- const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
13612
- item.evidenceId,
13613
- item.status
13614
- ]));
14060
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
13615
14061
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
13616
14062
  kind: item.data.kind,
13617
14063
  candidateId: item.data.candidateId,
@@ -13626,6 +14072,71 @@ class WorkitCore {
13626
14072
  return changed;
13627
14073
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
13628
14074
  }
14075
+ observeCheck(request) {
14076
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
14077
+ }
14078
+ observeCheckOnce(request) {
14079
+ const root = this.contextRootError();
14080
+ if (!root.ok)
14081
+ return root;
14082
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
14083
+ return failure2("permission_denied", "only the workit CLI records observed checks");
14084
+ const observation = checkObservationSchema.safeParse(request?.observation);
14085
+ if (!observation.success)
14086
+ return failure2("invalid_input", "check observation is invalid", {
14087
+ fields: observation.error.issues.map((issue) => ({
14088
+ path: issue.path.join("."),
14089
+ reason: issue.message
14090
+ }))
14091
+ });
14092
+ const task = this.store.readTask(request.taskId);
14093
+ if (!task.ok)
14094
+ return task;
14095
+ if (task.data.status === "closed")
14096
+ return failure2("invalid_transition", "closed task cannot record evidence");
14097
+ const observed = observation.data;
14098
+ let candidate = null;
14099
+ if (observed.tree === null) {
14100
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
14101
+ if (!captured.ok)
14102
+ return captured;
14103
+ candidate = captured.data;
14104
+ }
14105
+ const label = observed.name ?? observed.argv.join(" ");
14106
+ const data = {
14107
+ kind: "check",
14108
+ claim: `workit check ${label}`.slice(0, 500),
14109
+ requirementIds: [],
14110
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
14111
+ result: observed.exitCode === 0 ? "passed" : "failed",
14112
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
14113
+ refs: [],
14114
+ exitCode: observed.exitCode,
14115
+ reviewContext: null,
14116
+ observation: observed
14117
+ };
14118
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
14119
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
14120
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
14121
+ return success(mutation.revision, null, {
14122
+ ...current,
14123
+ critical,
14124
+ candidates,
14125
+ evidence: [
14126
+ ...current.evidence,
14127
+ {
14128
+ id: newId(),
14129
+ recordedAt: mutation.now,
14130
+ provenance: provenance(this.context, "host_observed"),
14131
+ data
14132
+ }
14133
+ ]
14134
+ });
14135
+ }, trustedNow(this.context));
14136
+ if (!changed.ok)
14137
+ return changed;
14138
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
14139
+ }
13629
14140
  decision(request) {
13630
14141
  return this.recordDecision(request);
13631
14142
  }
@@ -13861,7 +14372,7 @@ class WorkitCore {
13861
14372
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
13862
14373
  if (!current.ok)
13863
14374
  return current;
13864
- const evaluations = evaluateEvidence(task.data, current.data);
14375
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
13865
14376
  const verified = evidence.some((entry) => {
13866
14377
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
13867
14378
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -14565,7 +15076,8 @@ class WorkitCore {
14565
15076
  if (!current.ok)
14566
15077
  return current;
14567
15078
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14568
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15079
+ const tree = treeFreshness(this.store.root);
15080
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
14569
15081
  return success(task.revision, workspace.data.revision, {
14570
15082
  id: task.id,
14571
15083
  revision: task.revision,
@@ -14582,7 +15094,7 @@ class WorkitCore {
14582
15094
  writer: evaluationWorkspace.writer
14583
15095
  });
14584
15096
  }
14585
- compactContext(taskId) {
15097
+ compactContext(taskId, freshness) {
14586
15098
  const root = this.contextRootError();
14587
15099
  if (!root.ok)
14588
15100
  return root;
@@ -14592,12 +15104,12 @@ class WorkitCore {
14592
15104
  const helper = this.helperTaskGuard(task.data, true);
14593
15105
  if (!helper.ok)
14594
15106
  return helper;
14595
- const view = this.view(task.data, false);
15107
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
14596
15108
  if (!view.ok)
14597
15109
  return view;
14598
15110
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
14599
15111
  }
14600
- view(task, capture = true) {
15112
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
14601
15113
  const workspace = this.store.readWorkspace();
14602
15114
  if (!workspace.ok)
14603
15115
  return workspace;
@@ -14608,13 +15120,14 @@ class WorkitCore {
14608
15120
  if (!current.ok)
14609
15121
  return current;
14610
15122
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
14611
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
15123
+ const tree = freshness;
15124
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
14612
15125
  return success(task.revision, workspace.data.revision, {
14613
15126
  task,
14614
15127
  workspace: evaluationWorkspace,
14615
15128
  capabilities: this.context.capabilities,
14616
15129
  requirements,
14617
- evidence: evaluateEvidence(task, current.data)
15130
+ evidence: evaluateEvidence(task, current.data, tree)
14618
15131
  });
14619
15132
  }
14620
15133
  reconcileResume(view, observations = []) {
@@ -14841,13 +15354,14 @@ class WorkitCore {
14841
15354
  return current;
14842
15355
  const withCandidate = current.data;
14843
15356
  const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
14844
- const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller);
15357
+ const tree = treeFreshness(this.store.root);
15358
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
14845
15359
  const view = {
14846
15360
  task: taskForView,
14847
15361
  workspace: workspace.data,
14848
15362
  capabilities: this.context.capabilities,
14849
15363
  requirements,
14850
- evidence: evaluateEvidence(taskForView, withCandidate)
15364
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
14851
15365
  };
14852
15366
  const closure = evaluateClosure(input.outcome, view);
14853
15367
  if (!closure.ok)
@@ -15297,7 +15811,7 @@ var support = (descriptor, axis) => {
15297
15811
  });
15298
15812
 
15299
15813
  // packages/workit-core/src/hooks/context.ts
15300
- import path11 from "node:path";
15814
+ import path13 from "node:path";
15301
15815
  function sessionCompactContext(store, session, context, selection = "session-bound") {
15302
15816
  const listed = store.listTaskIndex();
15303
15817
  if (!listed.ok)
@@ -15320,18 +15834,26 @@ function entryCompactContext(store, entry, session, context) {
15320
15834
  capabilities: context.capabilities
15321
15835
  });
15322
15836
  const hit = cache.get(slot);
15323
- if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
15837
+ if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
15324
15838
  return hit.value;
15325
15839
  const task = store.readTask(entry.id);
15326
15840
  if (!task.ok)
15327
15841
  return null;
15328
- const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path11.resolve(store.root, ref.path)] : []));
15329
- const observed = filesKey(files);
15330
- const compact = new WorkitCore(store, context).compactContext(entry.id);
15842
+ const freshness = { mode: "signal", current: () => recentSignal(store.root) };
15843
+ const observed = task.data.evidence.some(isObservedCheck);
15844
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path13.resolve(store.root, ref.path)] : []));
15845
+ const observedFiles = filesKey(files);
15846
+ const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
15331
15847
  if (!compact.ok)
15332
15848
  return null;
15333
15849
  cache.delete(slot);
15334
- cache.set(slot, { key, files, filesKey: observed, value: compact.data });
15850
+ cache.set(slot, {
15851
+ key,
15852
+ files,
15853
+ filesKey: observedFiles,
15854
+ signal: observed ? freshness.current() : null,
15855
+ value: compact.data
15856
+ });
15335
15857
  if (cache.size > CACHE_LIMIT)
15336
15858
  cache.delete(cache.keys().next().value);
15337
15859
  return compact.data;
@@ -15352,7 +15874,17 @@ ${tasks.map((task) => `- ${task.id} [${task.status}; source ${task.source.host}/
15352
15874
  }, filesKey = (files) => {
15353
15875
  const signatures = files.map((file) => fileSignature(file) ?? "-");
15354
15876
  return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
15355
- }, CACHE_LIMIT = 64, cache, utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), hookOperationContext = (input, descriptor) => ({
15877
+ }, CACHE_LIMIT = 64, SIGNAL_TTL_MS = 1500, signals, recentSignal = (root) => {
15878
+ const now = Date.now();
15879
+ const cached = signals.get(root);
15880
+ if (cached && now - cached.at < SIGNAL_TTL_MS)
15881
+ return cached.value;
15882
+ const value = worktreeSignal(root);
15883
+ signals.set(root, { at: now, value });
15884
+ if (signals.size > CACHE_LIMIT)
15885
+ signals.delete(signals.keys().next().value);
15886
+ return value;
15887
+ }, cache, utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), hookOperationContext = (input, descriptor) => ({
15356
15888
  root: input.cwd,
15357
15889
  caller: { host: input.host, actor: input.session.id },
15358
15890
  callerAttested: false,
@@ -15395,8 +15927,11 @@ ${options.addendum}` : ""}
15395
15927
  var init_context = __esm(() => {
15396
15928
  init_methods();
15397
15929
  init_task_contract();
15930
+ init_task_evaluation();
15931
+ init_rev();
15398
15932
  init_task_engine();
15399
15933
  init_task_store();
15934
+ signals = new Map;
15400
15935
  cache = new Map;
15401
15936
  });
15402
15937
 
@@ -15504,9 +16039,9 @@ var init_handle = __esm(() => {
15504
16039
 
15505
16040
  // packages/workit-core/src/hooks/hosts/fields.ts
15506
16041
  import { existsSync as existsSync5, realpathSync as realpathSync7, statSync as statSync2 } from "node:fs";
15507
- import path12 from "node:path";
15508
- var isRecord2 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
15509
- if (!nonEmpty2(value) || !path12.isAbsolute(value) || !existsSync5(value))
16042
+ import path14 from "node:path";
16043
+ var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
16044
+ if (!nonEmpty2(value) || !path14.isAbsolute(value) || !existsSync5(value))
15510
16045
  return null;
15511
16046
  try {
15512
16047
  return statSync2(value).isDirectory() ? realpathSync7(value) : null;
@@ -15538,7 +16073,7 @@ var init_fields = __esm(() => {
15538
16073
  });
15539
16074
 
15540
16075
  // packages/workit-core/src/hooks/hosts/claude-code.ts
15541
- var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord2(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
16076
+ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
15542
16077
  const toolUseId = optionalText(value.tool_use_id);
15543
16078
  switch (name) {
15544
16079
  case "SessionStart":
@@ -15557,7 +16092,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
15557
16092
  const command = toolCommand(value);
15558
16093
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
15559
16094
  return { ok: false, error: "only shell PostToolUse is mapped" };
15560
- const response = isRecord2(value.tool_response) ? value.tool_response : {};
16095
+ const response = isRecord3(value.tool_response) ? value.tool_response : {};
15561
16096
  return {
15562
16097
  ok: true,
15563
16098
  event: {
@@ -15739,14 +16274,14 @@ var init_claude_code = __esm(() => {
15739
16274
  claudeCodeAdapter = {
15740
16275
  descriptor: CLAUDE_CODE_DESCRIPTOR,
15741
16276
  parse(raw) {
15742
- const native = isRecord2(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16277
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15743
16278
  const fail = (error) => ({
15744
16279
  ok: false,
15745
16280
  error,
15746
16281
  native,
15747
16282
  event: native ? EVENTS[native] : null
15748
16283
  });
15749
- if (!isRecord2(raw) || !native)
16284
+ if (!isRecord3(raw) || !native)
15750
16285
  return fail("hook_event_name is required");
15751
16286
  if (!nonEmpty2(raw.session_id))
15752
16287
  return fail("session_id is required");
@@ -15783,7 +16318,7 @@ function detectCodexSurface(env) {
15783
16318
  return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
15784
16319
  }
15785
16320
  var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase()), SOURCES2, parseCodexHookInput = (value) => {
15786
- if (!isRecord2(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
16321
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
15787
16322
  return { ok: false, error: "hook_event_name is required" };
15788
16323
  const event = value.hook_event_name;
15789
16324
  const cwd = existingDirectory(value.cwd);
@@ -15791,7 +16326,7 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
15791
16326
  return { ok: false, error: "cwd must be an existing absolute directory" };
15792
16327
  if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
15793
16328
  return { ok: false, error: "tool_name is required" };
15794
- const command = isRecord2(value.tool_input) ? commandText(value.tool_input.command) : null;
16329
+ const command = isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
15795
16330
  if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
15796
16331
  return { ok: false, error: "tool_input.command is required for shell tools" };
15797
16332
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -15985,7 +16520,7 @@ var init_codex = __esm(() => {
15985
16520
  const host = detectCodexSurface(env);
15986
16521
  const parsed = parseCodexHookInput(raw);
15987
16522
  if (!parsed.ok) {
15988
- const native = isRecord2(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16523
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15989
16524
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
15990
16525
  }
15991
16526
  const input = parsed.data;
@@ -16013,9 +16548,9 @@ var init_codex = __esm(() => {
16013
16548
  });
16014
16549
 
16015
16550
  // packages/workit-core/src/hooks/hosts/cursor.ts
16016
- import path13 from "node:path";
16551
+ import path15 from "node:path";
16017
16552
  var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
16018
- if (!isRecord2(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16553
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16019
16554
  return { ok: false, error: "hook_event_name is required" };
16020
16555
  const roots = value.workspace_roots;
16021
16556
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -16077,7 +16612,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
16077
16612
  case "preCompact":
16078
16613
  return { kind: "compact.pre", trigger: "auto" };
16079
16614
  }
16080
- }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path13.sep) ? root : `${root}${path13.sep}`), hookCwd = (input) => {
16615
+ }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path15.sep) ? root : `${root}${path15.sep}`), hookCwd = (input) => {
16081
16616
  const cwd = existingDirectory(input.cwd);
16082
16617
  if (cwd && input.hook_event_name === "beforeShellExecution")
16083
16618
  return cwd;
@@ -16229,7 +16764,7 @@ var init_cursor = __esm(() => {
16229
16764
  parse(raw) {
16230
16765
  const parsed = parseCursorHookInput(raw);
16231
16766
  if (!parsed.ok) {
16232
- const native = isRecord2(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16767
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16233
16768
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
16234
16769
  }
16235
16770
  const input = parsed.data;