@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.
@@ -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
@@ -13094,8 +13543,9 @@ target result; a local commit alone is not evidence of a requested remote push.
13094
13543
  Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
13095
13544
  substantial interruption or change of direction, workit-deslop when relevant
13096
13545
  to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
13097
- when impact is uncertain, and workit-challenge for genuinely open consequential
13098
- choices. Load workit-plan when dependencies or handoff need durable next actions.
13546
+ when impact is uncertain, workit-challenge for genuinely open consequential
13547
+ choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
13548
+ workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
13099
13549
  Load the skill; never act from memory of it.
13100
13550
  `.trim();
13101
13551
  var init_methods = __esm(() => {
@@ -13118,7 +13568,7 @@ function reconcileResume(view, observations = []) {
13118
13568
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
13119
13569
  if (!candidate.ok)
13120
13570
  return candidate;
13121
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13571
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
13122
13572
  const blockers = [...view.task.progress.blockers];
13123
13573
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
13124
13574
  blockers.push({
@@ -13608,10 +14058,7 @@ class WorkitCore {
13608
14058
  if (finding.data.disposition !== "fixed")
13609
14059
  return finding;
13610
14060
  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
- ]));
14061
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
13615
14062
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
13616
14063
  kind: item.data.kind,
13617
14064
  candidateId: item.data.candidateId,
@@ -13626,6 +14073,71 @@ class WorkitCore {
13626
14073
  return changed;
13627
14074
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
13628
14075
  }
14076
+ observeCheck(request) {
14077
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
14078
+ }
14079
+ observeCheckOnce(request) {
14080
+ const root = this.contextRootError();
14081
+ if (!root.ok)
14082
+ return root;
14083
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
14084
+ return failure2("permission_denied", "only the workit CLI records observed checks");
14085
+ const observation = checkObservationSchema.safeParse(request?.observation);
14086
+ if (!observation.success)
14087
+ return failure2("invalid_input", "check observation is invalid", {
14088
+ fields: observation.error.issues.map((issue) => ({
14089
+ path: issue.path.join("."),
14090
+ reason: issue.message
14091
+ }))
14092
+ });
14093
+ const task = this.store.readTask(request.taskId);
14094
+ if (!task.ok)
14095
+ return task;
14096
+ if (task.data.status === "closed")
14097
+ return failure2("invalid_transition", "closed task cannot record evidence");
14098
+ const observed = observation.data;
14099
+ let candidate = null;
14100
+ if (observed.tree === null) {
14101
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
14102
+ if (!captured.ok)
14103
+ return captured;
14104
+ candidate = captured.data;
14105
+ }
14106
+ const label = observed.name ?? observed.argv.join(" ");
14107
+ const data = {
14108
+ kind: "check",
14109
+ claim: `workit check ${label}`.slice(0, 500),
14110
+ requirementIds: [],
14111
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
14112
+ result: observed.exitCode === 0 ? "passed" : "failed",
14113
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
14114
+ refs: [],
14115
+ exitCode: observed.exitCode,
14116
+ reviewContext: null,
14117
+ observation: observed
14118
+ };
14119
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
14120
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
14121
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
14122
+ return success(mutation.revision, null, {
14123
+ ...current,
14124
+ critical,
14125
+ candidates,
14126
+ evidence: [
14127
+ ...current.evidence,
14128
+ {
14129
+ id: newId(),
14130
+ recordedAt: mutation.now,
14131
+ provenance: provenance(this.context, "host_observed"),
14132
+ data
14133
+ }
14134
+ ]
14135
+ });
14136
+ }, trustedNow(this.context));
14137
+ if (!changed.ok)
14138
+ return changed;
14139
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
14140
+ }
13629
14141
  decision(request) {
13630
14142
  return this.recordDecision(request);
13631
14143
  }
@@ -13861,7 +14373,7 @@ class WorkitCore {
13861
14373
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
13862
14374
  if (!current.ok)
13863
14375
  return current;
13864
- const evaluations = evaluateEvidence(task.data, current.data);
14376
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
13865
14377
  const verified = evidence.some((entry) => {
13866
14378
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
13867
14379
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -14565,7 +15077,8 @@ class WorkitCore {
14565
15077
  if (!current.ok)
14566
15078
  return current;
14567
15079
  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);
15080
+ const tree = treeFreshness(this.store.root);
15081
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
14569
15082
  return success(task.revision, workspace.data.revision, {
14570
15083
  id: task.id,
14571
15084
  revision: task.revision,
@@ -14582,7 +15095,7 @@ class WorkitCore {
14582
15095
  writer: evaluationWorkspace.writer
14583
15096
  });
14584
15097
  }
14585
- compactContext(taskId) {
15098
+ compactContext(taskId, freshness) {
14586
15099
  const root = this.contextRootError();
14587
15100
  if (!root.ok)
14588
15101
  return root;
@@ -14592,12 +15105,12 @@ class WorkitCore {
14592
15105
  const helper = this.helperTaskGuard(task.data, true);
14593
15106
  if (!helper.ok)
14594
15107
  return helper;
14595
- const view = this.view(task.data, false);
15108
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
14596
15109
  if (!view.ok)
14597
15110
  return view;
14598
15111
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
14599
15112
  }
14600
- view(task, capture = true) {
15113
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
14601
15114
  const workspace = this.store.readWorkspace();
14602
15115
  if (!workspace.ok)
14603
15116
  return workspace;
@@ -14608,13 +15121,14 @@ class WorkitCore {
14608
15121
  if (!current.ok)
14609
15122
  return current;
14610
15123
  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);
15124
+ const tree = freshness;
15125
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
14612
15126
  return success(task.revision, workspace.data.revision, {
14613
15127
  task,
14614
15128
  workspace: evaluationWorkspace,
14615
15129
  capabilities: this.context.capabilities,
14616
15130
  requirements,
14617
- evidence: evaluateEvidence(task, current.data)
15131
+ evidence: evaluateEvidence(task, current.data, tree)
14618
15132
  });
14619
15133
  }
14620
15134
  reconcileResume(view, observations = []) {
@@ -14841,13 +15355,14 @@ class WorkitCore {
14841
15355
  return current;
14842
15356
  const withCandidate = current.data;
14843
15357
  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);
15358
+ const tree = treeFreshness(this.store.root);
15359
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
14845
15360
  const view = {
14846
15361
  task: taskForView,
14847
15362
  workspace: workspace.data,
14848
15363
  capabilities: this.context.capabilities,
14849
15364
  requirements,
14850
- evidence: evaluateEvidence(taskForView, withCandidate)
15365
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
14851
15366
  };
14852
15367
  const closure = evaluateClosure(input.outcome, view);
14853
15368
  if (!closure.ok)
@@ -15297,7 +15812,7 @@ var support = (descriptor, axis) => {
15297
15812
  });
15298
15813
 
15299
15814
  // packages/workit-core/src/hooks/context.ts
15300
- import path11 from "node:path";
15815
+ import path13 from "node:path";
15301
15816
  function sessionCompactContext(store, session, context, selection = "session-bound") {
15302
15817
  const listed = store.listTaskIndex();
15303
15818
  if (!listed.ok)
@@ -15320,18 +15835,26 @@ function entryCompactContext(store, entry, session, context) {
15320
15835
  capabilities: context.capabilities
15321
15836
  });
15322
15837
  const hit = cache.get(slot);
15323
- if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
15838
+ if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
15324
15839
  return hit.value;
15325
15840
  const task = store.readTask(entry.id);
15326
15841
  if (!task.ok)
15327
15842
  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);
15843
+ const freshness = { mode: "signal", current: () => recentSignal(store.root) };
15844
+ const observed = task.data.evidence.some(isObservedCheck);
15845
+ const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path13.resolve(store.root, ref.path)] : []));
15846
+ const observedFiles = filesKey(files);
15847
+ const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
15331
15848
  if (!compact.ok)
15332
15849
  return null;
15333
15850
  cache.delete(slot);
15334
- cache.set(slot, { key, files, filesKey: observed, value: compact.data });
15851
+ cache.set(slot, {
15852
+ key,
15853
+ files,
15854
+ filesKey: observedFiles,
15855
+ signal: observed ? freshness.current() : null,
15856
+ value: compact.data
15857
+ });
15335
15858
  if (cache.size > CACHE_LIMIT)
15336
15859
  cache.delete(cache.keys().next().value);
15337
15860
  return compact.data;
@@ -15352,7 +15875,17 @@ ${tasks.map((task) => `- ${task.id} [${task.status}; source ${task.source.host}/
15352
15875
  }, filesKey = (files) => {
15353
15876
  const signatures = files.map((file) => fileSignature(file) ?? "-");
15354
15877
  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) => ({
15878
+ }, CACHE_LIMIT = 64, SIGNAL_TTL_MS = 1500, signals, recentSignal = (root) => {
15879
+ const now = Date.now();
15880
+ const cached = signals.get(root);
15881
+ if (cached && now - cached.at < SIGNAL_TTL_MS)
15882
+ return cached.value;
15883
+ const value = worktreeSignal(root);
15884
+ signals.set(root, { at: now, value });
15885
+ if (signals.size > CACHE_LIMIT)
15886
+ signals.delete(signals.keys().next().value);
15887
+ return value;
15888
+ }, cache, utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), hookOperationContext = (input, descriptor) => ({
15356
15889
  root: input.cwd,
15357
15890
  caller: { host: input.host, actor: input.session.id },
15358
15891
  callerAttested: false,
@@ -15395,8 +15928,11 @@ ${options.addendum}` : ""}
15395
15928
  var init_context = __esm(() => {
15396
15929
  init_methods();
15397
15930
  init_task_contract();
15931
+ init_task_evaluation();
15932
+ init_rev();
15398
15933
  init_task_engine();
15399
15934
  init_task_store();
15935
+ signals = new Map;
15400
15936
  cache = new Map;
15401
15937
  });
15402
15938
 
@@ -15504,9 +16040,9 @@ var init_handle = __esm(() => {
15504
16040
 
15505
16041
  // packages/workit-core/src/hooks/hosts/fields.ts
15506
16042
  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))
16043
+ import path14 from "node:path";
16044
+ 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) => {
16045
+ if (!nonEmpty2(value) || !path14.isAbsolute(value) || !existsSync5(value))
15510
16046
  return null;
15511
16047
  try {
15512
16048
  return statSync2(value).isDirectory() ? realpathSync7(value) : null;
@@ -15538,7 +16074,7 @@ var init_fields = __esm(() => {
15538
16074
  });
15539
16075
 
15540
16076
  // 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) => {
16077
+ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
15542
16078
  const toolUseId = optionalText(value.tool_use_id);
15543
16079
  switch (name) {
15544
16080
  case "SessionStart":
@@ -15557,7 +16093,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
15557
16093
  const command = toolCommand(value);
15558
16094
  if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
15559
16095
  return { ok: false, error: "only shell PostToolUse is mapped" };
15560
- const response = isRecord2(value.tool_response) ? value.tool_response : {};
16096
+ const response = isRecord3(value.tool_response) ? value.tool_response : {};
15561
16097
  return {
15562
16098
  ok: true,
15563
16099
  event: {
@@ -15739,14 +16275,14 @@ var init_claude_code = __esm(() => {
15739
16275
  claudeCodeAdapter = {
15740
16276
  descriptor: CLAUDE_CODE_DESCRIPTOR,
15741
16277
  parse(raw) {
15742
- const native = isRecord2(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16278
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15743
16279
  const fail = (error) => ({
15744
16280
  ok: false,
15745
16281
  error,
15746
16282
  native,
15747
16283
  event: native ? EVENTS[native] : null
15748
16284
  });
15749
- if (!isRecord2(raw) || !native)
16285
+ if (!isRecord3(raw) || !native)
15750
16286
  return fail("hook_event_name is required");
15751
16287
  if (!nonEmpty2(raw.session_id))
15752
16288
  return fail("session_id is required");
@@ -15783,7 +16319,7 @@ function detectCodexSurface(env) {
15783
16319
  return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
15784
16320
  }
15785
16321
  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)))
16322
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
15787
16323
  return { ok: false, error: "hook_event_name is required" };
15788
16324
  const event = value.hook_event_name;
15789
16325
  const cwd = existingDirectory(value.cwd);
@@ -15791,7 +16327,7 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
15791
16327
  return { ok: false, error: "cwd must be an existing absolute directory" };
15792
16328
  if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
15793
16329
  return { ok: false, error: "tool_name is required" };
15794
- const command = isRecord2(value.tool_input) ? commandText(value.tool_input.command) : null;
16330
+ const command = isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
15795
16331
  if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
15796
16332
  return { ok: false, error: "tool_input.command is required for shell tools" };
15797
16333
  if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
@@ -15985,7 +16521,7 @@ var init_codex = __esm(() => {
15985
16521
  const host = detectCodexSurface(env);
15986
16522
  const parsed = parseCodexHookInput(raw);
15987
16523
  if (!parsed.ok) {
15988
- const native = isRecord2(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16524
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
15989
16525
  return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
15990
16526
  }
15991
16527
  const input = parsed.data;
@@ -16013,9 +16549,9 @@ var init_codex = __esm(() => {
16013
16549
  });
16014
16550
 
16015
16551
  // packages/workit-core/src/hooks/hosts/cursor.ts
16016
- import path13 from "node:path";
16552
+ import path15 from "node:path";
16017
16553
  var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
16018
- if (!isRecord2(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16554
+ if (!isRecord3(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
16019
16555
  return { ok: false, error: "hook_event_name is required" };
16020
16556
  const roots = value.workspace_roots;
16021
16557
  if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
@@ -16077,7 +16613,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
16077
16613
  case "preCompact":
16078
16614
  return { kind: "compact.pre", trigger: "auto" };
16079
16615
  }
16080
- }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path13.sep) ? root : `${root}${path13.sep}`), hookCwd = (input) => {
16616
+ }, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path15.sep) ? root : `${root}${path15.sep}`), hookCwd = (input) => {
16081
16617
  const cwd = existingDirectory(input.cwd);
16082
16618
  if (cwd && input.hook_event_name === "beforeShellExecution")
16083
16619
  return cwd;
@@ -16229,7 +16765,7 @@ var init_cursor = __esm(() => {
16229
16765
  parse(raw) {
16230
16766
  const parsed = parseCursorHookInput(raw);
16231
16767
  if (!parsed.ok) {
16232
- const native = isRecord2(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16768
+ const native = isRecord3(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
16233
16769
  return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
16234
16770
  }
16235
16771
  const input = parsed.data;