@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.
- package/dist/index.js +627 -112
- 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
|
|
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
|
|
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
|
|
16786
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
16787
16787
|
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
16788
|
-
import
|
|
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
|
|
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
|
-
}
|
|
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:
|
|
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
|
|
20005
|
-
import * as
|
|
20006
|
-
import { spawnSync as
|
|
20007
|
-
import
|
|
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) =>
|
|
21704
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
21705
|
-
var relative = (root, candidate) =>
|
|
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 =
|
|
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 =
|
|
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(
|
|
21760
|
-
const stagedDeleted =
|
|
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(
|
|
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 =
|
|
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 =
|
|
22143
|
+
const target = path12.join(directory, name);
|
|
21782
22144
|
let stat;
|
|
21783
22145
|
try {
|
|
21784
|
-
stat =
|
|
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 =
|
|
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 (!
|
|
21804
|
-
ancestor =
|
|
22165
|
+
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
22166
|
+
ancestor = path12.dirname(ancestor);
|
|
21805
22167
|
try {
|
|
21806
|
-
if (!inside(root,
|
|
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 =
|
|
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 (!
|
|
22198
|
+
if (!fs10.existsSync(target))
|
|
21837
22199
|
continue;
|
|
21838
22200
|
let stat;
|
|
21839
22201
|
try {
|
|
21840
|
-
stat =
|
|
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 =
|
|
22218
|
+
const target = path12.join(checkout, item);
|
|
21857
22219
|
try {
|
|
21858
|
-
const stat =
|
|
22220
|
+
const stat = fs10.lstatSync(target);
|
|
21859
22221
|
if (stat.isSymbolicLink()) {
|
|
21860
|
-
const link =
|
|
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(
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
22023
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
24886
|
-
if (target !== base && !target.startsWith(base +
|
|
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 =
|
|
25410
|
+
ancestor = path13.dirname(ancestor);
|
|
24892
25411
|
const canonicalAncestor = realpathSync7(ancestor);
|
|
24893
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
|
24972
|
-
import
|
|
24973
|
-
import { spawnSync as
|
|
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(
|
|
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 =
|
|
25506
|
+
const candidate = path14.join(dir, name);
|
|
24988
25507
|
try {
|
|
24989
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
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 ?
|
|
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 =
|
|
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(
|
|
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("./" +
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
25579
|
-
import
|
|
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 ??
|
|
25593
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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 (!
|
|
26137
|
+
if (!fs12.existsSync(cfgPath))
|
|
25619
26138
|
return { data: {} };
|
|
25620
26139
|
let parsed;
|
|
25621
26140
|
try {
|
|
25622
|
-
parsed = JSON.parse(
|
|
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 ?
|
|
25662
|
-
if (!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 =
|
|
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 (!
|
|
26250
|
+
if (!fs12.existsSync(full))
|
|
25732
26251
|
continue;
|
|
25733
|
-
const text =
|
|
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,
|
|
26032
|
-
if (
|
|
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 !==
|
|
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 =
|
|
26653
|
+
const here = path17.dirname(fileURLToPath2(import.meta.url));
|
|
26139
26654
|
const sourceMarker = markSourcesLoaded([
|
|
26140
|
-
|
|
26141
|
-
|
|
26142
|
-
|
|
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] ?
|
|
26323
|
-
if (entryPath ===
|
|
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.
|
|
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.
|
|
45
|
+
"@brainervirus/workit-core": "^2.7.0",
|
|
46
46
|
"@modelcontextprotocol/sdk": "1.32.0",
|
|
47
47
|
"zod": "4.6.5"
|
|
48
48
|
},
|