@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.
@@ -6622,11 +6622,11 @@ var require_dist = __commonJS(function(exports, module) {
6622
6622
 
6623
6623
  // packages/workit-mcp/src/index.ts
6624
6624
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6625
- import path16 from "node:path";
6625
+ import path18 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-mcp/src/server.ts
6628
6628
  import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
6629
- import path15 from "node:path";
6629
+ import path17 from "node:path";
6630
6630
 
6631
6631
  // node_modules/zod/v4/core/util.js
6632
6632
  function getEnumValues(entries) {
@@ -16785,9 +16785,9 @@ class StdioServerTransport {
16785
16785
  import { fileURLToPath as fileURLToPath2 } from "node:url";
16786
16786
 
16787
16787
  // packages/workit-core/src/core.ts
16788
- import { spawnSync as spawnSync4 } from "node:child_process";
16788
+ import { spawnSync as spawnSync6 } from "node:child_process";
16789
16789
  import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
16790
- import path11 from "node:path";
16790
+ import path13 from "node:path";
16791
16791
 
16792
16792
  // packages/workit-core/src/core/task-contract.ts
16793
16793
  import { createHash, randomUUID } from "node:crypto";
@@ -17181,7 +17181,37 @@ var candidateSchema = object2({
17181
17181
  path: ["id"]
17182
17182
  });
17183
17183
  });
17184
- var evidenceSchema = object2({
17184
+ var checkObservationSchema = object2({
17185
+ observer: literal("workit_cli"),
17186
+ name: nonEmpty.nullable(),
17187
+ configured: boolean2(),
17188
+ argv: array(text).min(1),
17189
+ shell: boolean2(),
17190
+ cwd: nonEmpty,
17191
+ exitCode: safeInteger,
17192
+ durationMs: safeInteger.min(0),
17193
+ timedOut: boolean2(),
17194
+ head: text.nullable(),
17195
+ tree: text.nullable(),
17196
+ dirty: boolean2().nullable(),
17197
+ signal: text.nullable(),
17198
+ treeAfter: text.nullable(),
17199
+ modifiedWorktree: boolean2(),
17200
+ base: text.nullable(),
17201
+ patchId: text.nullable(),
17202
+ environment: object2({
17203
+ platform: nonEmpty,
17204
+ arch: nonEmpty,
17205
+ vars: record(nonEmpty, text.nullable())
17206
+ }).strict(),
17207
+ logDigest: text.nullable(),
17208
+ logRef: text.nullable(),
17209
+ logTail: array(text).max(40),
17210
+ ledgerRowId: text.nullable(),
17211
+ attestation: object2({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
17212
+ }).strict();
17213
+ var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
17214
+ var evidenceFields = {
17185
17215
  kind: _enum(["check", "review", "investigation", "artifact"]),
17186
17216
  claim: text,
17187
17217
  requirementIds: array(digest),
@@ -17192,7 +17222,9 @@ var evidenceSchema = object2({
17192
17222
  refs: array(refSchema),
17193
17223
  exitCode: safeInteger.nullable(),
17194
17224
  reviewContext: refSchema.nullable()
17195
- }).strict();
17225
+ };
17226
+ var evidenceSchema = object2({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
17227
+ var reportedEvidenceSchema = object2(evidenceFields).strict();
17196
17228
  var evidenceEvaluationSchema = object2({
17197
17229
  evidenceId: id,
17198
17230
  status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
@@ -17503,7 +17535,7 @@ var evidenceOperations = {
17503
17535
  action: literal("record"),
17504
17536
  ...taskId,
17505
17537
  expectedRevision: revision.optional(),
17506
- evidence: evidenceSchema
17538
+ evidence: reportedEvidenceSchema
17507
17539
  })
17508
17540
  };
17509
17541
  var findingOperations = {
@@ -20003,10 +20035,10 @@ function assertProductWriteAllowed(input) {
20003
20035
  }
20004
20036
 
20005
20037
  // packages/workit-core/src/core/task-evaluation.ts
20006
- import { createHash as createHash6 } from "node:crypto";
20007
- import * as fs8 from "node:fs";
20008
- import { spawnSync as spawnSync3 } from "node:child_process";
20009
- import path10 from "node:path";
20038
+ import { createHash as createHash7 } from "node:crypto";
20039
+ import * as fs10 from "node:fs";
20040
+ import { spawnSync as spawnSync5 } from "node:child_process";
20041
+ import path12 from "node:path";
20010
20042
 
20011
20043
  // packages/workit-core/src/core/authority.ts
20012
20044
  import { createHash as createHash5 } from "node:crypto";
@@ -21701,16 +21733,346 @@ function reconcileAction(input) {
21701
21733
  var bindingCovers = scopeCovers;
21702
21734
  var verifyDecisionContent = verifyContentRefs;
21703
21735
 
21736
+ // packages/workit-core/src/check-config.ts
21737
+ import { spawnSync as spawnSync3 } from "node:child_process";
21738
+ import fs8 from "node:fs";
21739
+ import path10 from "node:path";
21740
+ var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
21741
+ var CHECKS_FILE = "workit.checks.json";
21742
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21743
+ var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
21744
+ function splitCommand(command) {
21745
+ const argv = [];
21746
+ let current = "";
21747
+ let quote = null;
21748
+ let started = false;
21749
+ for (const char of command) {
21750
+ if (quote) {
21751
+ if (char === quote)
21752
+ quote = null;
21753
+ else
21754
+ current += char;
21755
+ continue;
21756
+ }
21757
+ if (char === '"' || char === "'") {
21758
+ quote = char;
21759
+ started = true;
21760
+ continue;
21761
+ }
21762
+ if (/\s/u.test(char)) {
21763
+ if (started)
21764
+ argv.push(current);
21765
+ current = "";
21766
+ started = false;
21767
+ continue;
21768
+ }
21769
+ if (SHELL_META.test(char))
21770
+ return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
21771
+ current += char;
21772
+ started = true;
21773
+ }
21774
+ if (quote)
21775
+ return new Error(`"${command}" has an unterminated quote`);
21776
+ if (started)
21777
+ argv.push(current);
21778
+ return argv.length ? argv : new Error("empty command");
21779
+ }
21780
+ var git = (cwd, args) => {
21781
+ const run = spawnSync3("git", args, {
21782
+ cwd,
21783
+ encoding: "utf8",
21784
+ timeout: 20000,
21785
+ killSignal: "SIGKILL",
21786
+ windowsHide: true
21787
+ });
21788
+ const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
21789
+ return value || null;
21790
+ };
21791
+ function checkRoot(cwd) {
21792
+ return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
21793
+ }
21794
+ var packageManager = (root) => {
21795
+ const has = (file) => fs8.existsSync(path10.join(root, file));
21796
+ if (has("bun.lock") || has("bun.lockb"))
21797
+ return "bun";
21798
+ if (has("pnpm-lock.yaml"))
21799
+ return "pnpm";
21800
+ if (has("yarn.lock"))
21801
+ return "yarn";
21802
+ return "npm";
21803
+ };
21804
+ var scriptCheck = (pm, name) => {
21805
+ const argv = [pm, "run", name];
21806
+ const accepts = [argv];
21807
+ if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
21808
+ accepts.push([pm, name]);
21809
+ return { name, argv, accepts };
21810
+ };
21811
+ var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
21812
+ function detectedChecks(root) {
21813
+ const read = (file) => {
21814
+ try {
21815
+ return fs8.readFileSync(path10.join(root, file), "utf8");
21816
+ } catch {
21817
+ return null;
21818
+ }
21819
+ };
21820
+ const checks = [];
21821
+ const add = (check) => {
21822
+ if (!checks.some((item) => item.name === check.name))
21823
+ checks.push(check);
21824
+ };
21825
+ const manifestText = read("package.json");
21826
+ if (manifestText !== null) {
21827
+ let manifest = null;
21828
+ try {
21829
+ manifest = JSON.parse(manifestText);
21830
+ } catch {}
21831
+ const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
21832
+ const pm = packageManager(root);
21833
+ for (const name of SCRIPT_CHECKS) {
21834
+ const script = scripts[name];
21835
+ if (typeof script === "string" && script.trim() !== "")
21836
+ add(scriptCheck(pm, name));
21837
+ }
21838
+ }
21839
+ const plain = (name, argv) => ({ name, argv, accepts: [argv] });
21840
+ if (read("go.mod") !== null)
21841
+ add(plain("test", ["go", "test", "./..."]));
21842
+ if (read("Cargo.toml") !== null)
21843
+ add(plain("test", ["cargo", "test"]));
21844
+ if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
21845
+ add(plain("test", ["pytest"]));
21846
+ const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
21847
+ if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
21848
+ add(plain("test", ["make", "test"]));
21849
+ return checks;
21850
+ }
21851
+ var GATES = ["testing", "verification"];
21852
+ function loadCheckConfig(cwd) {
21853
+ const root = checkRoot(cwd);
21854
+ const file = path10.join(root, CHECKS_FILE);
21855
+ const none = (error = null) => ({
21856
+ source: error ? CHECKS_FILE : "none",
21857
+ root,
21858
+ checks: [],
21859
+ gates: {},
21860
+ error
21861
+ });
21862
+ let text = null;
21863
+ try {
21864
+ text = fs8.readFileSync(file, "utf8");
21865
+ } catch (error) {
21866
+ if (error.code !== "ENOENT")
21867
+ return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
21868
+ }
21869
+ if (text === null) {
21870
+ const checks = detectedChecks(root);
21871
+ return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
21872
+ }
21873
+ let parsed;
21874
+ try {
21875
+ parsed = JSON.parse(text);
21876
+ } catch {
21877
+ return none(`${CHECKS_FILE} is not valid JSON`);
21878
+ }
21879
+ if (!isRecord2(parsed) || !isRecord2(parsed.checks))
21880
+ return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
21881
+ const checks = [];
21882
+ for (const [name, value] of Object.entries(parsed.checks)) {
21883
+ if (!CHECK_NAME.test(name))
21884
+ return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
21885
+ 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");
21886
+ if (argv instanceof Error)
21887
+ return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
21888
+ checks.push({ name, argv, accepts: [argv] });
21889
+ }
21890
+ const gates = {};
21891
+ if (parsed.gates !== undefined) {
21892
+ if (!isRecord2(parsed.gates))
21893
+ return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
21894
+ for (const [gate, value] of Object.entries(parsed.gates)) {
21895
+ if (!GATES.includes(gate))
21896
+ return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
21897
+ const names = typeof value === "string" ? [value] : value;
21898
+ if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
21899
+ return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
21900
+ gates[gate] = names;
21901
+ }
21902
+ }
21903
+ return { source: CHECKS_FILE, root, checks, gates, error: null };
21904
+ }
21905
+ var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
21906
+ function matchesNamedCheck(config, name, argv, cwd) {
21907
+ if (config.error || name === null || cwd !== ".")
21908
+ return false;
21909
+ const check = config.checks.find((item) => item.name === name);
21910
+ return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
21911
+ }
21912
+ function gateCheckNames(config, dimension) {
21913
+ const explicit = config.gates[dimension];
21914
+ if (explicit)
21915
+ return explicit;
21916
+ const names = config.checks.map((check) => check.name);
21917
+ if (dimension === "testing")
21918
+ return names.includes("test") ? ["test"] : [];
21919
+ return names;
21920
+ }
21921
+ var workitOnPath;
21922
+ function workitAvailable(env = process.env) {
21923
+ if (workitOnPath !== undefined)
21924
+ return workitOnPath;
21925
+ const windows = process.platform === "win32";
21926
+ const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
21927
+ const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
21928
+ workitOnPath = dirs.some((dir) => exts.some((ext) => {
21929
+ try {
21930
+ return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
21931
+ } catch {
21932
+ return false;
21933
+ }
21934
+ }));
21935
+ return workitOnPath;
21936
+ }
21937
+ function checkCommand(args, version, onPath = workitAvailable()) {
21938
+ return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
21939
+ }
21940
+
21941
+ // packages/workit-core/src/git/rev.ts
21942
+ import { spawnSync as spawnSync4 } from "node:child_process";
21943
+ import { createHash as createHash6 } from "node:crypto";
21944
+ import fs9 from "node:fs";
21945
+ import os2 from "node:os";
21946
+ import path11 from "node:path";
21947
+ var MAX_BUFFER = 256 * 1024 * 1024;
21948
+ var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
21949
+ var git2 = (cwd, args, options = {}) => {
21950
+ const result = spawnSync4("git", args, {
21951
+ cwd,
21952
+ encoding: "utf8",
21953
+ env: options.env ?? process.env,
21954
+ input: options.input,
21955
+ maxBuffer: MAX_BUFFER,
21956
+ timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
21957
+ killSignal: "SIGKILL",
21958
+ windowsHide: true
21959
+ });
21960
+ const error = result.error;
21961
+ return {
21962
+ ok: result.status === 0,
21963
+ stdout: result.stdout ?? "",
21964
+ stderr: result.stderr ?? "",
21965
+ timedOut: error?.code === "ETIMEDOUT"
21966
+ };
21967
+ };
21968
+ var line = (run) => {
21969
+ if (!run.ok)
21970
+ return null;
21971
+ const value = run.stdout.trim();
21972
+ return value ? value : null;
21973
+ };
21974
+ function headSha(cwd) {
21975
+ return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
21976
+ }
21977
+ var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
21978
+ var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
21979
+ var seedIndex = (from, to) => {
21980
+ fs9.copyFileSync(from, to);
21981
+ const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
21982
+ const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
21983
+ fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
21984
+ };
21985
+ function worktreeTree(cwd, options = {}) {
21986
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
21987
+ const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
21988
+ if (!top || !indexPath)
21989
+ return null;
21990
+ const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
21991
+ const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
21992
+ const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
21993
+ try {
21994
+ const tempIndex = path11.join(scratch, "index");
21995
+ if (fs9.existsSync(indexPath))
21996
+ seedIndex(indexPath, tempIndex);
21997
+ const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
21998
+ const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
21999
+ env,
22000
+ timeoutMs
22001
+ });
22002
+ if (!untracked.ok)
22003
+ return null;
22004
+ const skipped = [];
22005
+ for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
22006
+ try {
22007
+ const stat = fs9.statSync(path11.join(top, file));
22008
+ if (stat.isFile() && stat.size > cap)
22009
+ skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
22010
+ } catch {}
22011
+ }
22012
+ skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
22013
+ const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
22014
+ env,
22015
+ timeoutMs,
22016
+ input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
22017
+ }) : git2(top, ["add", "-A"], { env, timeoutMs });
22018
+ if (!added.ok)
22019
+ return null;
22020
+ const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
22021
+ if (!tree)
22022
+ return null;
22023
+ const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
22024
+ const key = skipped.length ? `sha256:${createHash6("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
22025
+ return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
22026
+ } finally {
22027
+ fs9.rmSync(scratch, { recursive: true, force: true });
22028
+ }
22029
+ }
22030
+ function worktreeSignal(cwd, options = {}) {
22031
+ const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
22032
+ if (!top)
22033
+ return null;
22034
+ const status = git2(top, [
22035
+ "status",
22036
+ "--porcelain=v1",
22037
+ "-z",
22038
+ "--untracked-files=all",
22039
+ "--no-renames",
22040
+ "--ignore-submodules=dirty"
22041
+ ], {
22042
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
22043
+ timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
22044
+ });
22045
+ if (!status.ok)
22046
+ return null;
22047
+ const hash = createHash6("sha256");
22048
+ hash.update(`${headSha(top) ?? "unborn"}\x00`);
22049
+ for (const entry of status.stdout.split("\x00")) {
22050
+ if (entry.length < 4)
22051
+ continue;
22052
+ const file = entry.slice(3);
22053
+ const absolute = path11.join(top, file);
22054
+ let stat = "-";
22055
+ try {
22056
+ const info = fs9.lstatSync(absolute, { bigint: true });
22057
+ stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
22058
+ if (info.isDirectory())
22059
+ stat += `:${headSha(absolute) ?? "-"}`;
22060
+ } catch {}
22061
+ hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
22062
+ }
22063
+ return `sig:${hash.digest("hex")}`;
22064
+ }
22065
+
21704
22066
  // packages/workit-core/src/core/task-evaluation.ts
21705
- var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
21706
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
21707
- var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
22067
+ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex");
22068
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`);
22069
+ var relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".";
21708
22070
  var canonicalPath = (value) => {
21709
22071
  if (typeof value !== "string" || !value)
21710
22072
  return null;
21711
22073
  if (value.includes("\\") || value.split("/").includes(".."))
21712
22074
  return null;
21713
- const normalized = path10.posix.normalize(value);
22075
+ const normalized = path12.posix.normalize(value);
21714
22076
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
21715
22077
  return null;
21716
22078
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -21746,7 +22108,7 @@ var compareCodeUnits2 = (left, right) => {
21746
22108
  return left.length - right.length;
21747
22109
  };
21748
22110
  var gitPaths = (root) => {
21749
- const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
22111
+ const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
21750
22112
  cwd: root,
21751
22113
  encoding: "buffer"
21752
22114
  });
@@ -21758,21 +22120,21 @@ var gitPaths = (root) => {
21758
22120
  git: !/not a git repository/i.test(stderr)
21759
22121
  };
21760
22122
  }
21761
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
21762
- const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
22123
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
22124
+ const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
21763
22125
  if (stagedDeleted.status !== 0) {
21764
22126
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
21765
22127
  if (!/not a git repository/i.test(stderr))
21766
22128
  return { paths, uncertain: true, git: true };
21767
22129
  } else if (stagedDeleted.stdout) {
21768
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")));
22130
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
21769
22131
  }
21770
22132
  return { paths: [...new Set(paths)], uncertain: false, git: true };
21771
22133
  };
21772
22134
  var walk = (root, directory, output) => {
21773
22135
  let names;
21774
22136
  try {
21775
- names = fs8.readdirSync(directory);
22137
+ names = fs10.readdirSync(directory);
21776
22138
  } catch {
21777
22139
  return true;
21778
22140
  }
@@ -21780,10 +22142,10 @@ var walk = (root, directory, output) => {
21780
22142
  for (const name of names) {
21781
22143
  if (name === ".git" || name === ".workit")
21782
22144
  continue;
21783
- const target = path10.join(directory, name);
22145
+ const target = path12.join(directory, name);
21784
22146
  let stat;
21785
22147
  try {
21786
- stat = fs8.lstatSync(target);
22148
+ stat = fs10.lstatSync(target);
21787
22149
  } catch {
21788
22150
  uncertain = true;
21789
22151
  continue;
@@ -21798,14 +22160,14 @@ var walk = (root, directory, output) => {
21798
22160
  var scopeRoots = (root, scope) => {
21799
22161
  const roots = [];
21800
22162
  for (const item of scope.paths.length ? scope.paths : ["."]) {
21801
- const target = path10.resolve(root, item);
22163
+ const target = path12.resolve(root, item);
21802
22164
  if (!inside(root, target))
21803
22165
  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` }] });
21804
22166
  let ancestor = target;
21805
- while (!fs8.existsSync(ancestor) && ancestor !== root)
21806
- ancestor = path10.dirname(ancestor);
22167
+ while (!fs10.existsSync(ancestor) && ancestor !== root)
22168
+ ancestor = path12.dirname(ancestor);
21807
22169
  try {
21808
- if (!inside(root, fs8.realpathSync(ancestor)))
22170
+ if (!inside(root, fs10.realpathSync(ancestor)))
21809
22171
  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` }] });
21810
22172
  } catch {
21811
22173
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -21817,7 +22179,7 @@ var scopeRoots = (root, scope) => {
21817
22179
  function captureCandidate(root, scope, environment = []) {
21818
22180
  let checkout;
21819
22181
  try {
21820
- checkout = fs8.realpathSync(root);
22182
+ checkout = fs10.realpathSync(root);
21821
22183
  } catch {
21822
22184
  return failure2("invalid_input", "candidate root does not exist");
21823
22185
  }
@@ -21835,11 +22197,11 @@ function captureCandidate(root, scope, environment = []) {
21835
22197
  if (item === ".git" || item === ".workit")
21836
22198
  continue;
21837
22199
  names.add(item);
21838
- if (!fs8.existsSync(target))
22200
+ if (!fs10.existsSync(target))
21839
22201
  continue;
21840
22202
  let stat;
21841
22203
  try {
21842
- stat = fs8.lstatSync(target);
22204
+ stat = fs10.lstatSync(target);
21843
22205
  } catch {
21844
22206
  uncertain = true;
21845
22207
  continue;
@@ -21855,17 +22217,17 @@ function captureCandidate(root, scope, environment = []) {
21855
22217
  for (const item of [...names].toSorted()) {
21856
22218
  if (!scopeMatches(item, normalizedScope))
21857
22219
  continue;
21858
- const target = path10.join(checkout, item);
22220
+ const target = path12.join(checkout, item);
21859
22221
  try {
21860
- const stat = fs8.lstatSync(target);
22222
+ const stat = fs10.lstatSync(target);
21861
22223
  if (stat.isSymbolicLink()) {
21862
- const link = fs8.readlinkSync(target);
22224
+ const link = fs10.readlinkSync(target);
21863
22225
  files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
21864
22226
  } else if (stat.isFile()) {
21865
22227
  files.push({
21866
22228
  path: item,
21867
22229
  kind: "file",
21868
- digest: digestBytes3(fs8.readFileSync(target)),
22230
+ digest: digestBytes3(fs10.readFileSync(target)),
21869
22231
  executable: (stat.mode & 73) !== 0
21870
22232
  });
21871
22233
  } else if (!stat.isDirectory()) {
@@ -21890,7 +22252,7 @@ function captureCandidate(root, scope, environment = []) {
21890
22252
  const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
21891
22253
  if (values.some(({ value }) => value === null))
21892
22254
  completeness = "uncertain";
21893
- const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
22255
+ const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
21894
22256
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
21895
22257
  completeness = "uncertain";
21896
22258
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -21928,7 +22290,24 @@ var sameSession = (left, right) => {
21928
22290
  return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
21929
22291
  };
21930
22292
  var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
21931
- function evaluateEvidence(task, candidate) {
22293
+ var memo2 = (compute) => {
22294
+ let value;
22295
+ return () => {
22296
+ if (value === undefined)
22297
+ value = compute();
22298
+ return value;
22299
+ };
22300
+ };
22301
+ var treeFreshness = (root) => ({
22302
+ mode: "tree",
22303
+ current: memo2(() => worktreeTree(root)?.key ?? null)
22304
+ });
22305
+ var signalFreshness = (root) => ({
22306
+ mode: "signal",
22307
+ current: memo2(() => worktreeSignal(root))
22308
+ });
22309
+ var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
22310
+ function evaluateEvidence(task, candidate, freshness) {
21932
22311
  const current = candidate ?? task.candidates.at(-1) ?? null;
21933
22312
  return task.evidence.map((entry) => {
21934
22313
  const evidence = entry.data;
@@ -21938,6 +22317,25 @@ function evaluateEvidence(task, candidate) {
21938
22317
  status: "failed",
21939
22318
  reason: "failed evidence remains historical"
21940
22319
  };
22320
+ const observation = isObservedCheck(entry) ? evidence.observation : undefined;
22321
+ if (observation && observation.tree !== null) {
22322
+ if (task.status === "closed")
22323
+ return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
22324
+ if (observation.modifiedWorktree)
22325
+ return {
22326
+ evidenceId: entry.id,
22327
+ status: "stale",
22328
+ reason: "the check changed the worktree while it ran; ignore or clean its outputs"
22329
+ };
22330
+ const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
22331
+ if (recorded !== null && freshness && recorded === freshness.current())
22332
+ return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
22333
+ return {
22334
+ evidenceId: entry.id,
22335
+ status: "stale",
22336
+ 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"
22337
+ };
22338
+ }
21941
22339
  if (evidence.kind === "check" || evidence.kind === "review") {
21942
22340
  if (!evidence.beforeCandidateId || !evidence.candidateId)
21943
22341
  return {
@@ -22021,8 +22419,45 @@ var expectedKinds = (dimension, ruleId) => {
22021
22419
  return "decision";
22022
22420
  return "worker report";
22023
22421
  };
22024
- function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
22025
- const evidence = evaluateEvidence(task, candidate);
22422
+ var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
22423
+ function evaluateObservedGate(task, requirement, statuses, config) {
22424
+ const names = gateCheckNames(config, requirement.dimension);
22425
+ const observed = task.evidence.filter(isObservedCheck);
22426
+ const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
22427
+ const named = (entry) => {
22428
+ const observation = entry.data.observation;
22429
+ return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
22430
+ };
22431
+ const considered = observed.map((entry) => entry.id);
22432
+ const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
22433
+ if (config.error)
22434
+ return {
22435
+ satisfied: [],
22436
+ considered,
22437
+ reason: `check config is invalid (${config.error}); fix it, then run ${run}`
22438
+ };
22439
+ if (!names.length)
22440
+ return {
22441
+ satisfied: [],
22442
+ considered,
22443
+ 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())}`
22444
+ };
22445
+ const satisfied = fresh.filter(named);
22446
+ if (satisfied.length)
22447
+ return {
22448
+ satisfied: satisfied.map((entry) => entry.id),
22449
+ considered,
22450
+ reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
22451
+ };
22452
+ const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
22453
+ 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}`;
22454
+ return { satisfied: [], considered, reason };
22455
+ }
22456
+ function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
22457
+ const evidence = evaluateEvidence(task, candidate, freshness);
22458
+ const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
22459
+ let checkConfig;
22460
+ const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
22026
22461
  if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
22027
22462
  return task.policy.requirements.map((requirement) => ({
22028
22463
  requirementId: requirement.id,
@@ -22032,8 +22467,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
22032
22467
  reason: "stored policy version is unsupported; reassessment is required"
22033
22468
  }));
22034
22469
  return (task.policy?.requirements ?? []).map((requirement) => {
22470
+ const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
22471
+ if (gate?.satisfied.length)
22472
+ return {
22473
+ requirementId: requirement.id,
22474
+ status: "satisfied",
22475
+ evidenceIds: gate.satisfied,
22476
+ decisionIds: [],
22477
+ reason: gate.reason
22478
+ };
22035
22479
  const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
22036
22480
  const passed = related.filter(({ entry, evaluation }) => {
22481
+ if (gate)
22482
+ return false;
22037
22483
  if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
22038
22484
  return false;
22039
22485
  if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
@@ -22096,6 +22542,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
22096
22542
  reason: "an applicable approved limitation permits the missing evidence"
22097
22543
  };
22098
22544
  const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
22545
+ if (gate)
22546
+ return {
22547
+ requirementId: requirement.id,
22548
+ status: unavailable ? "unavailable" : "unsatisfied",
22549
+ evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
22550
+ decisionIds: [],
22551
+ reason: unavailable ? "required capability is unavailable" : gate.reason
22552
+ };
22099
22553
  if (!unavailable && related.length > 0 && !passed.length)
22100
22554
  return {
22101
22555
  requirementId: requirement.id,
@@ -22248,7 +22702,7 @@ function reconcileResume(view, observations = []) {
22248
22702
  const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
22249
22703
  if (!candidate.ok)
22250
22704
  return candidate;
22251
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
22705
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
22252
22706
  const blockers = [...view.task.progress.blockers];
22253
22707
  if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
22254
22708
  blockers.push({
@@ -23588,10 +24042,7 @@ class WorkitCore {
23588
24042
  if (finding.data.disposition !== "fixed")
23589
24043
  return finding;
23590
24044
  const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
23591
- const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
23592
- item.evidenceId,
23593
- item.status
23594
- ]));
24045
+ const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
23595
24046
  const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
23596
24047
  kind: item.data.kind,
23597
24048
  candidateId: item.data.candidateId,
@@ -23606,6 +24057,71 @@ class WorkitCore {
23606
24057
  return changed;
23607
24058
  return success(changed.data.revision, null, changed.data.evidence.at(-1));
23608
24059
  }
24060
+ observeCheck(request) {
24061
+ return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
24062
+ }
24063
+ observeCheckOnce(request) {
24064
+ const root = this.contextRootError();
24065
+ if (!root.ok)
24066
+ return root;
24067
+ if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
24068
+ return failure2("permission_denied", "only the workit CLI records observed checks");
24069
+ const observation = checkObservationSchema.safeParse(request?.observation);
24070
+ if (!observation.success)
24071
+ return failure2("invalid_input", "check observation is invalid", {
24072
+ fields: observation.error.issues.map((issue) => ({
24073
+ path: issue.path.join("."),
24074
+ reason: issue.message
24075
+ }))
24076
+ });
24077
+ const task = this.store.readTask(request.taskId);
24078
+ if (!task.ok)
24079
+ return task;
24080
+ if (task.data.status === "closed")
24081
+ return failure2("invalid_transition", "closed task cannot record evidence");
24082
+ const observed = observation.data;
24083
+ let candidate = null;
24084
+ if (observed.tree === null) {
24085
+ const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
24086
+ if (!captured.ok)
24087
+ return captured;
24088
+ candidate = captured.data;
24089
+ }
24090
+ const label = observed.name ?? observed.argv.join(" ");
24091
+ const data = {
24092
+ kind: "check",
24093
+ claim: `workit check ${label}`.slice(0, 500),
24094
+ requirementIds: [],
24095
+ ...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
24096
+ result: observed.exitCode === 0 ? "passed" : "failed",
24097
+ summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
24098
+ refs: [],
24099
+ exitCode: observed.exitCode,
24100
+ reviewContext: null,
24101
+ observation: observed
24102
+ };
24103
+ const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
24104
+ const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
24105
+ const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
24106
+ return success(mutation.revision, null, {
24107
+ ...current,
24108
+ critical,
24109
+ candidates,
24110
+ evidence: [
24111
+ ...current.evidence,
24112
+ {
24113
+ id: newId(),
24114
+ recordedAt: mutation.now,
24115
+ provenance: provenance(this.context, "host_observed"),
24116
+ data
24117
+ }
24118
+ ]
24119
+ });
24120
+ }, trustedNow(this.context));
24121
+ if (!changed.ok)
24122
+ return changed;
24123
+ return success(changed.data.revision, null, changed.data.evidence.at(-1));
24124
+ }
23609
24125
  decision(request) {
23610
24126
  return this.recordDecision(request);
23611
24127
  }
@@ -23841,7 +24357,7 @@ class WorkitCore {
23841
24357
  const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
23842
24358
  if (!current.ok)
23843
24359
  return current;
23844
- const evaluations = evaluateEvidence(task.data, current.data);
24360
+ const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
23845
24361
  const verified = evidence.some((entry) => {
23846
24362
  const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
23847
24363
  return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
@@ -24545,7 +25061,8 @@ class WorkitCore {
24545
25061
  if (!current.ok)
24546
25062
  return current;
24547
25063
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
24548
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
25064
+ const tree = treeFreshness(this.store.root);
25065
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
24549
25066
  return success(task.revision, workspace.data.revision, {
24550
25067
  id: task.id,
24551
25068
  revision: task.revision,
@@ -24562,7 +25079,7 @@ class WorkitCore {
24562
25079
  writer: evaluationWorkspace.writer
24563
25080
  });
24564
25081
  }
24565
- compactContext(taskId) {
25082
+ compactContext(taskId, freshness) {
24566
25083
  const root = this.contextRootError();
24567
25084
  if (!root.ok)
24568
25085
  return root;
@@ -24572,12 +25089,12 @@ class WorkitCore {
24572
25089
  const helper = this.helperTaskGuard(task.data, true);
24573
25090
  if (!helper.ok)
24574
25091
  return helper;
24575
- const view = this.view(task.data, false);
25092
+ const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
24576
25093
  if (!view.ok)
24577
25094
  return view;
24578
25095
  return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
24579
25096
  }
24580
- view(task, capture = true) {
25097
+ view(task, capture = true, freshness = treeFreshness(this.store.root)) {
24581
25098
  const workspace = this.store.readWorkspace();
24582
25099
  if (!workspace.ok)
24583
25100
  return workspace;
@@ -24588,13 +25105,14 @@ class WorkitCore {
24588
25105
  if (!current.ok)
24589
25106
  return current;
24590
25107
  const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
24591
- const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller);
25108
+ const tree = freshness;
25109
+ const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
24592
25110
  return success(task.revision, workspace.data.revision, {
24593
25111
  task,
24594
25112
  workspace: evaluationWorkspace,
24595
25113
  capabilities: this.context.capabilities,
24596
25114
  requirements,
24597
- evidence: evaluateEvidence(task, current.data)
25115
+ evidence: evaluateEvidence(task, current.data, tree)
24598
25116
  });
24599
25117
  }
24600
25118
  reconcileResume(view, observations = []) {
@@ -24821,13 +25339,14 @@ class WorkitCore {
24821
25339
  return current;
24822
25340
  const withCandidate = current.data;
24823
25341
  const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
24824
- const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller);
25342
+ const tree = treeFreshness(this.store.root);
25343
+ const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
24825
25344
  const view = {
24826
25345
  task: taskForView,
24827
25346
  workspace: workspace.data,
24828
25347
  capabilities: this.context.capabilities,
24829
25348
  requirements,
24830
- evidence: evaluateEvidence(taskForView, withCandidate)
25349
+ evidence: evaluateEvidence(taskForView, withCandidate, tree)
24831
25350
  };
24832
25351
  const closure = evaluateClosure(input.outcome, view);
24833
25352
  if (!closure.ok)
@@ -24884,22 +25403,22 @@ function resolveGitRevision(root, value) {
24884
25403
  }
24885
25404
  function resolveInside(root, candidate) {
24886
25405
  const base = realpathSync7(root);
24887
- const target = path11.resolve(base, candidate);
24888
- if (target !== base && !target.startsWith(base + path11.sep)) {
25406
+ const target = path13.resolve(base, candidate);
25407
+ if (target !== base && !target.startsWith(base + path13.sep)) {
24889
25408
  throw new Error("path must stay inside repository root");
24890
25409
  }
24891
25410
  let ancestor = target;
24892
25411
  while (!existsSync5(ancestor))
24893
- ancestor = path11.dirname(ancestor);
25412
+ ancestor = path13.dirname(ancestor);
24894
25413
  const canonicalAncestor = realpathSync7(ancestor);
24895
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path11.sep)) {
25414
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
24896
25415
  throw new Error("path must stay inside repository root");
24897
25416
  }
24898
25417
  return target;
24899
25418
  }
24900
25419
  function run2(root, executable, args, env = {}) {
24901
25420
  const cwd = realpathSync7(root);
24902
- const result = spawnSync4(executable, args, {
25421
+ const result = spawnSync6(executable, args, {
24903
25422
  cwd,
24904
25423
  encoding: "utf8",
24905
25424
  env: { ...process.env, ...env }
@@ -24913,7 +25432,7 @@ function run2(root, executable, args, env = {}) {
24913
25432
  }
24914
25433
 
24915
25434
  // packages/workit-core/src/core/logger.ts
24916
- import os2 from "node:os";
25435
+ import os3 from "node:os";
24917
25436
  var REDACTED = "[REDACTED]";
24918
25437
  var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
24919
25438
  var KEY_EQ_VALUE = /\b([A-Za-z0-9_-]*(?:token|secret|password|passwd|apikey|api[_-]?key|authorization|credential|bearer)[A-Za-z0-9_-]*)([:=]\s*).+/gi;
@@ -24956,7 +25475,7 @@ var SENSITIVE_WORDS = new Set([
24956
25475
  ]);
24957
25476
  var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
24958
25477
  var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24959
- var homePattern = new RegExp(`${escapeRegExp(os2.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
25478
+ var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
24960
25479
  var applyPatterns = (value) => {
24961
25480
  let out = value;
24962
25481
  out = out.replace(SECRET_VALUE, REDACTED);
@@ -24970,9 +25489,9 @@ var applyPatterns = (value) => {
24970
25489
  var redactSecrets = (value) => applyPatterns(value);
24971
25490
 
24972
25491
  // packages/workit-core/src/core/pr-create.ts
24973
- import fs9 from "node:fs";
24974
- import path12 from "node:path";
24975
- import { spawnSync as spawnSync5 } from "node:child_process";
25492
+ import fs11 from "node:fs";
25493
+ import path14 from "node:path";
25494
+ import { spawnSync as spawnSync7 } from "node:child_process";
24976
25495
  // packages/workit-core/src/forge/pr-body.ts
24977
25496
  function parseGhIssue(value) {
24978
25497
  const m = /issues\/(\d+)/.exec(value);
@@ -24982,13 +25501,13 @@ function parseGhIssue(value) {
24982
25501
  // packages/workit-core/src/core/pr-create.ts
24983
25502
  function whichOnPath(tool) {
24984
25503
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
24985
- for (const dir of (process.env.PATH ?? "").split(path12.delimiter)) {
25504
+ for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
24986
25505
  if (!dir)
24987
25506
  continue;
24988
25507
  for (const name of names) {
24989
- const candidate = path12.join(dir, name);
25508
+ const candidate = path14.join(dir, name);
24990
25509
  try {
24991
- fs9.accessSync(candidate, fs9.constants.X_OK);
25510
+ fs11.accessSync(candidate, fs11.constants.X_OK);
24992
25511
  return candidate;
24993
25512
  } catch {}
24994
25513
  }
@@ -24997,7 +25516,7 @@ function whichOnPath(tool) {
24997
25516
  }
24998
25517
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
24999
25518
  var pushRemote = (cwd) => {
25000
- const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
25519
+ const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
25001
25520
  cwd,
25002
25521
  encoding: "utf8"
25003
25522
  });
@@ -25008,9 +25527,9 @@ var pushRemote = (cwd) => {
25008
25527
  };
25009
25528
 
25010
25529
  // packages/workit-core/src/core/repo-context.ts
25011
- import { spawnSync as spawnSync6 } from "node:child_process";
25530
+ import { spawnSync as spawnSync8 } from "node:child_process";
25012
25531
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
25013
- import path13 from "node:path";
25532
+ import path15 from "node:path";
25014
25533
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
25015
25534
  try {
25016
25535
  gitRevisionParts(value);
@@ -25037,7 +25556,7 @@ var invalidRange = (cwd) => ({
25037
25556
  cwd
25038
25557
  });
25039
25558
  var runGit = (cwd, args) => {
25040
- const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
25559
+ const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
25041
25560
  return {
25042
25561
  stdout: (result.stdout ?? "").trimEnd(),
25043
25562
  stderr: (result.stderr ?? "").trimEnd(),
@@ -25144,7 +25663,7 @@ var findPrTemplate = (cwd) => {
25144
25663
  ]) {
25145
25664
  const parts = rel.split("/");
25146
25665
  const base = parts[parts.length - 1];
25147
- const dir = parts.length > 1 ? path13.join(cwd, ...parts.slice(0, -1)) : cwd;
25666
+ const dir = parts.length > 1 ? path15.join(cwd, ...parts.slice(0, -1)) : cwd;
25148
25667
  const actual = probeCaseInsensitive(dir, base);
25149
25668
  if (actual)
25150
25669
  return [...parts.slice(0, -1), actual].join("/");
@@ -25175,7 +25694,7 @@ var diffStatForRange = (cwd, range) => {
25175
25694
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
25176
25695
  };
25177
25696
  var packageScripts = (cwd, keys) => {
25178
- const pkgPath = path13.join(cwd, "package.json");
25697
+ const pkgPath = path15.join(cwd, "package.json");
25179
25698
  if (!existsSync6(pkgPath))
25180
25699
  return [];
25181
25700
  try {
@@ -25201,9 +25720,9 @@ var documentationFiles = (cwd) => {
25201
25720
  if (entry.isDirectory()) {
25202
25721
  if (excluded.has(entry.name))
25203
25722
  continue;
25204
- walk(path13.join(dir, entry.name), depth + 1);
25723
+ walk(path15.join(dir, entry.name), depth + 1);
25205
25724
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
25206
- matches.push("./" + path13.relative(cwd, path13.join(dir, entry.name)).replaceAll("\\", "/"));
25725
+ matches.push("./" + path15.relative(cwd, path15.join(dir, entry.name)).replaceAll("\\", "/"));
25207
25726
  }
25208
25727
  }
25209
25728
  };
@@ -25219,7 +25738,7 @@ var readTrimmed2 = (file, maxLines) => {
25219
25738
  `);
25220
25739
  };
25221
25740
  function prReadyContext(root, range) {
25222
- const cwd = path13.resolve(repoRoot(root));
25741
+ const cwd = path15.resolve(repoRoot(root));
25223
25742
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25224
25743
  return invalidRange(cwd);
25225
25744
  let stdout = "";
@@ -25277,7 +25796,7 @@ ${status.stderr}` : "");
25277
25796
  stdout += `template_path: ${template}
25278
25797
 
25279
25798
  `;
25280
- stdout += readTrimmed2(path13.join(cwd, template), 220);
25799
+ stdout += readTrimmed2(path15.join(cwd, template), 220);
25281
25800
  } else {
25282
25801
  stdout += `template_path: none
25283
25802
 
@@ -25287,7 +25806,7 @@ ${status.stderr}` : "");
25287
25806
  stdout += `
25288
25807
  `;
25289
25808
  stdout += printSection("Recent Validation Signals");
25290
- if (existsSync6(path13.join(cwd, "package.json"))) {
25809
+ if (existsSync6(path15.join(cwd, "package.json"))) {
25291
25810
  stdout += `package.json detected. Common scripts:
25292
25811
  `;
25293
25812
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -25296,7 +25815,7 @@ ${status.stderr}` : "");
25296
25815
  `) + `
25297
25816
  `;
25298
25817
  }
25299
- if (existsSync6(path13.join(cwd, "Cargo.toml")) || existsSync6(path13.join(cwd, "src-tauri/Cargo.toml"))) {
25818
+ if (existsSync6(path15.join(cwd, "Cargo.toml")) || existsSync6(path15.join(cwd, "src-tauri/Cargo.toml"))) {
25300
25819
  stdout += `Rust project detected.
25301
25820
  `;
25302
25821
  }
@@ -25329,7 +25848,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
25329
25848
  - Apply with the host-native editor, preserving the repository changelog conventions.
25330
25849
  - If Unreleased already has duplicate category headings, normalize_only first.`;
25331
25850
  function changelogContext(root, range) {
25332
- const cwd = path13.resolve(repoRoot(root));
25851
+ const cwd = path15.resolve(repoRoot(root));
25333
25852
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25334
25853
  return invalidRange(cwd);
25335
25854
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25347,7 +25866,7 @@ function changelogContext(root, range) {
25347
25866
  stdout += CHANGELOG_RULES + `
25348
25867
  `;
25349
25868
  stdout += printSection("Existing CHANGELOG.md");
25350
- const changelogPath = path13.join(cwd, "CHANGELOG.md");
25869
+ const changelogPath = path15.join(cwd, "CHANGELOG.md");
25351
25870
  if (existsSync6(changelogPath)) {
25352
25871
  stdout += readTrimmed2(changelogPath, 260) + `
25353
25872
  `;
@@ -25367,7 +25886,7 @@ function changelogContext(root, range) {
25367
25886
  return { stdout, stderr: "", exitCode: 0, cwd };
25368
25887
  }
25369
25888
  function docsRefreshContext(root, range) {
25370
- const cwd = path13.resolve(repoRoot(root));
25889
+ const cwd = path15.resolve(repoRoot(root));
25371
25890
  if (range !== undefined && !isResolvableContextRange(cwd, range))
25372
25891
  return invalidRange(cwd);
25373
25892
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -25389,7 +25908,7 @@ function docsRefreshContext(root, range) {
25389
25908
  `) + `
25390
25909
  `;
25391
25910
  stdout += printSection("README Preview");
25392
- const readme = path13.join(cwd, "README.md");
25911
+ const readme = path15.join(cwd, "README.md");
25393
25912
  if (existsSync6(readme)) {
25394
25913
  stdout += readTrimmed2(readme, 220) + `
25395
25914
  `;
@@ -25398,7 +25917,7 @@ function docsRefreshContext(root, range) {
25398
25917
  `;
25399
25918
  }
25400
25919
  stdout += printSection("Package Scripts");
25401
- const pkgPath = path13.join(cwd, "package.json");
25920
+ const pkgPath = path15.join(cwd, "package.json");
25402
25921
  if (existsSync6(pkgPath)) {
25403
25922
  try {
25404
25923
  const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
@@ -25412,7 +25931,7 @@ function docsRefreshContext(root, range) {
25412
25931
  return { stdout, stderr: "", exitCode: 0, cwd };
25413
25932
  }
25414
25933
  function releaseNotesContext(root, rangeOrTag) {
25415
- const cwd = path13.resolve(repoRoot(root));
25934
+ const cwd = path15.resolve(repoRoot(root));
25416
25935
  if (!rangeOrTag) {
25417
25936
  return { stdout: "", stderr: `ERROR: release tag or range required
25418
25937
  `, exitCode: 1, cwd };
@@ -25450,7 +25969,7 @@ function releaseNotesContext(root, rangeOrTag) {
25450
25969
  `;
25451
25970
  stdout += printSection("Existing Release Files");
25452
25971
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
25453
- if (existsSync6(path13.join(cwd, rel)))
25972
+ if (existsSync6(path15.join(cwd, rel)))
25454
25973
  stdout += rel + `
25455
25974
  `;
25456
25975
  }
@@ -25478,7 +25997,7 @@ function releaseNotesContext(root, rangeOrTag) {
25478
25997
  }
25479
25998
 
25480
25999
  // packages/workit-core/src/core/tracker-issues.ts
25481
- import { spawnSync as spawnSync7 } from "node:child_process";
26000
+ import { spawnSync as spawnSync9 } from "node:child_process";
25482
26001
  var repoPathFromRemote = (remote) => {
25483
26002
  let rest = (remote || "").trim().replace(/\/+$/, "");
25484
26003
  if (!rest)
@@ -25499,7 +26018,7 @@ var defaultCli = (bin, available) => {
25499
26018
  if (!available)
25500
26019
  return null;
25501
26020
  return (args, root) => {
25502
- const out = spawnSync7(bin, args, {
26021
+ const out = spawnSync9(bin, args, {
25503
26022
  cwd: root,
25504
26023
  encoding: "utf8",
25505
26024
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -25577,8 +26096,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
25577
26096
  };
25578
26097
 
25579
26098
  // packages/workit-core/src/core/youtrack.ts
25580
- import fs10 from "node:fs";
25581
- import path14 from "node:path";
26099
+ import fs12 from "node:fs";
26100
+ import path16 from "node:path";
25582
26101
 
25583
26102
  // packages/workit-core/src/core/templates.ts
25584
26103
  var repoRoot2 = assetRoot();
@@ -25591,22 +26110,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
25591
26110
  // packages/workit-core/src/core/youtrack.ts
25592
26111
  var ISSUE_RE = /^[A-Z]+-\d+$/;
25593
26112
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
25594
- var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(configDir(), "youtrack.json");
25595
- var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path14.join(resolveConfigDir(), "youtrack.json");
26113
+ var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path16.join(configDir(), "youtrack.json");
26114
+ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path16.join(resolveConfigDir(), "youtrack.json");
25596
26115
  var youTrackTokenModeOk = (p) => {
25597
26116
  if (process.platform === "win32")
25598
26117
  return true;
25599
- const mode = fs10.statSync(p).mode & 511;
26118
+ const mode = fs12.statSync(p).mode & 511;
25600
26119
  return mode === 384;
25601
26120
  };
25602
26121
  function readYouTrackConfig(required) {
25603
26122
  const cfgPath = youTrackConfigPath();
25604
- if (!fs10.existsSync(cfgPath)) {
26123
+ if (!fs12.existsSync(cfgPath)) {
25605
26124
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
25606
26125
  }
25607
26126
  let parsed;
25608
26127
  try {
25609
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
26128
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
25610
26129
  } catch {
25611
26130
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
25612
26131
  }
@@ -25617,11 +26136,11 @@ function readYouTrackConfig(required) {
25617
26136
  }
25618
26137
  var readYouTrackContextConfig = () => {
25619
26138
  const cfgPath = youTrackReadOnlyConfigPath();
25620
- if (!fs10.existsSync(cfgPath))
26139
+ if (!fs12.existsSync(cfgPath))
25621
26140
  return { data: {} };
25622
26141
  let parsed;
25623
26142
  try {
25624
- parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
26143
+ parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
25625
26144
  } catch {
25626
26145
  return { error: "YouTrack configuration is unavailable" };
25627
26146
  }
@@ -25660,12 +26179,12 @@ var youTrackToken = () => {
25660
26179
  if ("error" in loaded)
25661
26180
  return loaded;
25662
26181
  const tokenFile = String(loaded.config.tokenFile ?? "");
25663
- const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
25664
- if (!tokenPath || !fs10.existsSync(tokenPath))
26182
+ const tokenPath = tokenFile ? path16.isAbsolute(tokenFile) ? path16.resolve(tokenFile) : path16.resolve(process.cwd(), tokenFile) : "";
26183
+ if (!tokenPath || !fs12.existsSync(tokenPath))
25665
26184
  return { error: "missing youtrack.token" };
25666
26185
  if (!youTrackTokenModeOk(tokenPath))
25667
26186
  return { error: "youtrack.token mode must be 0600" };
25668
- const token = fs10.readFileSync(tokenPath, "utf8").trim();
26187
+ const token = fs12.readFileSync(tokenPath, "utf8").trim();
25669
26188
  if (!token)
25670
26189
  return { error: "empty token file" };
25671
26190
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -25730,9 +26249,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
25730
26249
  } catch {
25731
26250
  continue;
25732
26251
  }
25733
- if (!fs10.existsSync(full))
26252
+ if (!fs12.existsSync(full))
25734
26253
  continue;
25735
- const text = fs10.readFileSync(full, "utf8");
26254
+ const text = fs12.readFileSync(full, "utf8");
25736
26255
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
25737
26256
  if (m)
25738
26257
  return m[1];
@@ -25885,10 +26404,6 @@ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\
25885
26404
  var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
25886
26405
  var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
25887
26406
 
25888
- // packages/workit-core/src/git/rev.ts
25889
- var MAX_BUFFER = 256 * 1024 * 1024;
25890
- var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
25891
-
25892
26407
  // packages/workit-core/src/ledger.ts
25893
26408
  var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
25894
26409
  var FAILING = new Set(["failed", "blocked"]);
@@ -26030,13 +26545,13 @@ var rootVariants = (workspaceRoot) => {
26030
26545
  if (!workspaceRoot)
26031
26546
  return [];
26032
26547
  const roots = new Set;
26033
- for (const candidate of [workspaceRoot, path15.resolve(workspaceRoot)]) {
26034
- if (path15.isAbsolute(candidate) && candidate !== path15.parse(candidate).root)
26548
+ for (const candidate of [workspaceRoot, path17.resolve(workspaceRoot)]) {
26549
+ if (path17.isAbsolute(candidate) && candidate !== path17.parse(candidate).root)
26035
26550
  roots.add(candidate);
26036
26551
  }
26037
26552
  try {
26038
26553
  const canonical = realpathSync8(workspaceRoot);
26039
- if (canonical !== path15.parse(canonical).root)
26554
+ if (canonical !== path17.parse(canonical).root)
26040
26555
  roots.add(canonical);
26041
26556
  } catch {}
26042
26557
  return [...roots].toSorted((left, right) => right.length - left.length);
@@ -26137,11 +26652,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
26137
26652
  };
26138
26653
  function createMcpServer(host, contextProvider) {
26139
26654
  assertMcpHost(host);
26140
- const here = path15.dirname(fileURLToPath2(import.meta.url));
26655
+ const here = path17.dirname(fileURLToPath2(import.meta.url));
26141
26656
  const sourceMarker = markSourcesLoaded([
26142
- path15.join(here, "server.ts"),
26143
- path15.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26144
- path15.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26657
+ path17.join(here, "server.ts"),
26658
+ path17.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
26659
+ path17.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
26145
26660
  ].filter((file) => existsSync7(file)));
26146
26661
  let staleWarned = false;
26147
26662
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
@@ -26321,8 +26836,8 @@ var main = async () => {
26321
26836
  process.exitCode = 2;
26322
26837
  }
26323
26838
  };
26324
- var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
26325
- if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
26839
+ var entryPath = process.argv[1] ? path18.resolve(process.argv[1]) : "";
26840
+ if (entryPath === path18.resolve(fileURLToPath3(import.meta.url)))
26326
26841
  await main();
26327
26842
  // packages/workit-core/src/hooks/descriptor.ts
26328
26843
  var support = (descriptor, axis) => {
@@ -26349,6 +26864,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
26349
26864
  };
26350
26865
  });
26351
26866
  // packages/workit-core/src/hooks/context.ts
26867
+ var signals = new Map;
26352
26868
  var cache = new Map;
26353
26869
  // packages/workit-core/src/hooks/handle.ts
26354
26870
  var offered = new Set;