@brainervirus/workit-mcp 2.6.0 → 2.7.0

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