@brainervirus/workit-codex 2.5.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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +629 -112
- package/dist/workit-hook.js +1138 -594
- package/package.json +3 -3
- package/skills/workit-behavioral-tdd/SKILL.md +15 -9
- package/skills/workit-debug/SKILL.md +2 -1
package/dist/launch-mcp.js
CHANGED
|
@@ -6622,12 +6622,12 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6622
6622
|
|
|
6623
6623
|
// packages/workit-codex/scripts/launch-mcp.ts
|
|
6624
6624
|
import { existsSync as existsSync8, realpathSync as realpathSync9, statSync as statSync3 } from "node:fs";
|
|
6625
|
-
import
|
|
6625
|
+
import path18 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-core/src/core.ts
|
|
6628
|
-
import { spawnSync as
|
|
6628
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6629
6629
|
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
6630
|
-
import
|
|
6630
|
+
import path13 from "node:path";
|
|
6631
6631
|
|
|
6632
6632
|
// packages/workit-core/src/core/task-contract.ts
|
|
6633
6633
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -14803,7 +14803,37 @@ var candidateSchema = object({
|
|
|
14803
14803
|
path: ["id"]
|
|
14804
14804
|
});
|
|
14805
14805
|
});
|
|
14806
|
-
var
|
|
14806
|
+
var checkObservationSchema = object({
|
|
14807
|
+
observer: literal("workit_cli"),
|
|
14808
|
+
name: nonEmpty.nullable(),
|
|
14809
|
+
configured: boolean2(),
|
|
14810
|
+
argv: array(text).min(1),
|
|
14811
|
+
shell: boolean2(),
|
|
14812
|
+
cwd: nonEmpty,
|
|
14813
|
+
exitCode: safeInteger,
|
|
14814
|
+
durationMs: safeInteger.min(0),
|
|
14815
|
+
timedOut: boolean2(),
|
|
14816
|
+
head: text.nullable(),
|
|
14817
|
+
tree: text.nullable(),
|
|
14818
|
+
dirty: boolean2().nullable(),
|
|
14819
|
+
signal: text.nullable(),
|
|
14820
|
+
treeAfter: text.nullable(),
|
|
14821
|
+
modifiedWorktree: boolean2(),
|
|
14822
|
+
base: text.nullable(),
|
|
14823
|
+
patchId: text.nullable(),
|
|
14824
|
+
environment: object({
|
|
14825
|
+
platform: nonEmpty,
|
|
14826
|
+
arch: nonEmpty,
|
|
14827
|
+
vars: record(nonEmpty, text.nullable())
|
|
14828
|
+
}).strict(),
|
|
14829
|
+
logDigest: text.nullable(),
|
|
14830
|
+
logRef: text.nullable(),
|
|
14831
|
+
logTail: array(text).max(40),
|
|
14832
|
+
ledgerRowId: text.nullable(),
|
|
14833
|
+
attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
|
|
14834
|
+
}).strict();
|
|
14835
|
+
var CHECK_OBSERVATION_PATH = "evidence.*.data.observation";
|
|
14836
|
+
var evidenceFields = {
|
|
14807
14837
|
kind: _enum(["check", "review", "investigation", "artifact"]),
|
|
14808
14838
|
claim: text,
|
|
14809
14839
|
requirementIds: array(digest),
|
|
@@ -14814,7 +14844,9 @@ var evidenceSchema = object({
|
|
|
14814
14844
|
refs: array(refSchema),
|
|
14815
14845
|
exitCode: safeInteger.nullable(),
|
|
14816
14846
|
reviewContext: refSchema.nullable()
|
|
14817
|
-
}
|
|
14847
|
+
};
|
|
14848
|
+
var evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
|
|
14849
|
+
var reportedEvidenceSchema = object(evidenceFields).strict();
|
|
14818
14850
|
var evidenceEvaluationSchema = object({
|
|
14819
14851
|
evidenceId: id,
|
|
14820
14852
|
status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
|
|
@@ -15125,7 +15157,7 @@ var evidenceOperations = {
|
|
|
15125
15157
|
action: literal("record"),
|
|
15126
15158
|
...taskId,
|
|
15127
15159
|
expectedRevision: revision.optional(),
|
|
15128
|
-
evidence:
|
|
15160
|
+
evidence: reportedEvidenceSchema
|
|
15129
15161
|
})
|
|
15130
15162
|
};
|
|
15131
15163
|
var findingOperations = {
|
|
@@ -17625,10 +17657,10 @@ function assertProductWriteAllowed(input) {
|
|
|
17625
17657
|
}
|
|
17626
17658
|
|
|
17627
17659
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
17628
|
-
import { createHash as
|
|
17629
|
-
import * as
|
|
17630
|
-
import { spawnSync as
|
|
17631
|
-
import
|
|
17660
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
17661
|
+
import * as fs10 from "node:fs";
|
|
17662
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
17663
|
+
import path12 from "node:path";
|
|
17632
17664
|
|
|
17633
17665
|
// packages/workit-core/src/core/authority.ts
|
|
17634
17666
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -19323,16 +19355,346 @@ function reconcileAction(input) {
|
|
|
19323
19355
|
var bindingCovers = scopeCovers;
|
|
19324
19356
|
var verifyDecisionContent = verifyContentRefs;
|
|
19325
19357
|
|
|
19358
|
+
// packages/workit-core/src/check-config.ts
|
|
19359
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
19360
|
+
import fs8 from "node:fs";
|
|
19361
|
+
import path10 from "node:path";
|
|
19362
|
+
var SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
19363
|
+
var CHECKS_FILE = "workit.checks.json";
|
|
19364
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19365
|
+
var SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
19366
|
+
function splitCommand(command) {
|
|
19367
|
+
const argv = [];
|
|
19368
|
+
let current = "";
|
|
19369
|
+
let quote = null;
|
|
19370
|
+
let started = false;
|
|
19371
|
+
for (const char of command) {
|
|
19372
|
+
if (quote) {
|
|
19373
|
+
if (char === quote)
|
|
19374
|
+
quote = null;
|
|
19375
|
+
else
|
|
19376
|
+
current += char;
|
|
19377
|
+
continue;
|
|
19378
|
+
}
|
|
19379
|
+
if (char === '"' || char === "'") {
|
|
19380
|
+
quote = char;
|
|
19381
|
+
started = true;
|
|
19382
|
+
continue;
|
|
19383
|
+
}
|
|
19384
|
+
if (/\s/u.test(char)) {
|
|
19385
|
+
if (started)
|
|
19386
|
+
argv.push(current);
|
|
19387
|
+
current = "";
|
|
19388
|
+
started = false;
|
|
19389
|
+
continue;
|
|
19390
|
+
}
|
|
19391
|
+
if (SHELL_META.test(char))
|
|
19392
|
+
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
19393
|
+
current += char;
|
|
19394
|
+
started = true;
|
|
19395
|
+
}
|
|
19396
|
+
if (quote)
|
|
19397
|
+
return new Error(`"${command}" has an unterminated quote`);
|
|
19398
|
+
if (started)
|
|
19399
|
+
argv.push(current);
|
|
19400
|
+
return argv.length ? argv : new Error("empty command");
|
|
19401
|
+
}
|
|
19402
|
+
var git = (cwd, args) => {
|
|
19403
|
+
const run = spawnSync3("git", args, {
|
|
19404
|
+
cwd,
|
|
19405
|
+
encoding: "utf8",
|
|
19406
|
+
timeout: 20000,
|
|
19407
|
+
killSignal: "SIGKILL",
|
|
19408
|
+
windowsHide: true
|
|
19409
|
+
});
|
|
19410
|
+
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
19411
|
+
return value || null;
|
|
19412
|
+
};
|
|
19413
|
+
function checkRoot(cwd) {
|
|
19414
|
+
return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
19415
|
+
}
|
|
19416
|
+
var packageManager = (root) => {
|
|
19417
|
+
const has = (file) => fs8.existsSync(path10.join(root, file));
|
|
19418
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
19419
|
+
return "bun";
|
|
19420
|
+
if (has("pnpm-lock.yaml"))
|
|
19421
|
+
return "pnpm";
|
|
19422
|
+
if (has("yarn.lock"))
|
|
19423
|
+
return "yarn";
|
|
19424
|
+
return "npm";
|
|
19425
|
+
};
|
|
19426
|
+
var scriptCheck = (pm, name) => {
|
|
19427
|
+
const argv = [pm, "run", name];
|
|
19428
|
+
const accepts = [argv];
|
|
19429
|
+
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
19430
|
+
accepts.push([pm, name]);
|
|
19431
|
+
return { name, argv, accepts };
|
|
19432
|
+
};
|
|
19433
|
+
var CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
19434
|
+
function detectedChecks(root) {
|
|
19435
|
+
const read = (file) => {
|
|
19436
|
+
try {
|
|
19437
|
+
return fs8.readFileSync(path10.join(root, file), "utf8");
|
|
19438
|
+
} catch {
|
|
19439
|
+
return null;
|
|
19440
|
+
}
|
|
19441
|
+
};
|
|
19442
|
+
const checks = [];
|
|
19443
|
+
const add = (check) => {
|
|
19444
|
+
if (!checks.some((item) => item.name === check.name))
|
|
19445
|
+
checks.push(check);
|
|
19446
|
+
};
|
|
19447
|
+
const manifestText = read("package.json");
|
|
19448
|
+
if (manifestText !== null) {
|
|
19449
|
+
let manifest = null;
|
|
19450
|
+
try {
|
|
19451
|
+
manifest = JSON.parse(manifestText);
|
|
19452
|
+
} catch {}
|
|
19453
|
+
const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
|
|
19454
|
+
const pm = packageManager(root);
|
|
19455
|
+
for (const name of SCRIPT_CHECKS) {
|
|
19456
|
+
const script = scripts[name];
|
|
19457
|
+
if (typeof script === "string" && script.trim() !== "")
|
|
19458
|
+
add(scriptCheck(pm, name));
|
|
19459
|
+
}
|
|
19460
|
+
}
|
|
19461
|
+
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
19462
|
+
if (read("go.mod") !== null)
|
|
19463
|
+
add(plain("test", ["go", "test", "./..."]));
|
|
19464
|
+
if (read("Cargo.toml") !== null)
|
|
19465
|
+
add(plain("test", ["cargo", "test"]));
|
|
19466
|
+
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
19467
|
+
add(plain("test", ["pytest"]));
|
|
19468
|
+
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
19469
|
+
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
19470
|
+
add(plain("test", ["make", "test"]));
|
|
19471
|
+
return checks;
|
|
19472
|
+
}
|
|
19473
|
+
var GATES = ["testing", "verification"];
|
|
19474
|
+
function loadCheckConfig(cwd) {
|
|
19475
|
+
const root = checkRoot(cwd);
|
|
19476
|
+
const file = path10.join(root, CHECKS_FILE);
|
|
19477
|
+
const none = (error = null) => ({
|
|
19478
|
+
source: error ? CHECKS_FILE : "none",
|
|
19479
|
+
root,
|
|
19480
|
+
checks: [],
|
|
19481
|
+
gates: {},
|
|
19482
|
+
error
|
|
19483
|
+
});
|
|
19484
|
+
let text = null;
|
|
19485
|
+
try {
|
|
19486
|
+
text = fs8.readFileSync(file, "utf8");
|
|
19487
|
+
} catch (error) {
|
|
19488
|
+
if (error.code !== "ENOENT")
|
|
19489
|
+
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
19490
|
+
}
|
|
19491
|
+
if (text === null) {
|
|
19492
|
+
const checks = detectedChecks(root);
|
|
19493
|
+
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
19494
|
+
}
|
|
19495
|
+
let parsed;
|
|
19496
|
+
try {
|
|
19497
|
+
parsed = JSON.parse(text);
|
|
19498
|
+
} catch {
|
|
19499
|
+
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
19500
|
+
}
|
|
19501
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.checks))
|
|
19502
|
+
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
19503
|
+
const checks = [];
|
|
19504
|
+
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
19505
|
+
if (!CHECK_NAME.test(name))
|
|
19506
|
+
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
19507
|
+
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");
|
|
19508
|
+
if (argv instanceof Error)
|
|
19509
|
+
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
19510
|
+
checks.push({ name, argv, accepts: [argv] });
|
|
19511
|
+
}
|
|
19512
|
+
const gates = {};
|
|
19513
|
+
if (parsed.gates !== undefined) {
|
|
19514
|
+
if (!isRecord2(parsed.gates))
|
|
19515
|
+
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
19516
|
+
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
19517
|
+
if (!GATES.includes(gate))
|
|
19518
|
+
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
19519
|
+
const names = typeof value === "string" ? [value] : value;
|
|
19520
|
+
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
19521
|
+
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
19522
|
+
gates[gate] = names;
|
|
19523
|
+
}
|
|
19524
|
+
}
|
|
19525
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
19526
|
+
}
|
|
19527
|
+
var sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]);
|
|
19528
|
+
function matchesNamedCheck(config, name, argv, cwd) {
|
|
19529
|
+
if (config.error || name === null || cwd !== ".")
|
|
19530
|
+
return false;
|
|
19531
|
+
const check = config.checks.find((item) => item.name === name);
|
|
19532
|
+
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
19533
|
+
}
|
|
19534
|
+
function gateCheckNames(config, dimension) {
|
|
19535
|
+
const explicit = config.gates[dimension];
|
|
19536
|
+
if (explicit)
|
|
19537
|
+
return explicit;
|
|
19538
|
+
const names = config.checks.map((check) => check.name);
|
|
19539
|
+
if (dimension === "testing")
|
|
19540
|
+
return names.includes("test") ? ["test"] : [];
|
|
19541
|
+
return names;
|
|
19542
|
+
}
|
|
19543
|
+
var workitOnPath;
|
|
19544
|
+
function workitAvailable(env = process.env) {
|
|
19545
|
+
if (workitOnPath !== undefined)
|
|
19546
|
+
return workitOnPath;
|
|
19547
|
+
const windows = process.platform === "win32";
|
|
19548
|
+
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
19549
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
|
|
19550
|
+
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
19551
|
+
try {
|
|
19552
|
+
return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
|
|
19553
|
+
} catch {
|
|
19554
|
+
return false;
|
|
19555
|
+
}
|
|
19556
|
+
}));
|
|
19557
|
+
return workitOnPath;
|
|
19558
|
+
}
|
|
19559
|
+
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
19560
|
+
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
19561
|
+
}
|
|
19562
|
+
|
|
19563
|
+
// packages/workit-core/src/git/rev.ts
|
|
19564
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
19565
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
19566
|
+
import fs9 from "node:fs";
|
|
19567
|
+
import os2 from "node:os";
|
|
19568
|
+
import path11 from "node:path";
|
|
19569
|
+
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
19570
|
+
var GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
19571
|
+
var git2 = (cwd, args, options = {}) => {
|
|
19572
|
+
const result = spawnSync4("git", args, {
|
|
19573
|
+
cwd,
|
|
19574
|
+
encoding: "utf8",
|
|
19575
|
+
env: options.env ?? process.env,
|
|
19576
|
+
input: options.input,
|
|
19577
|
+
maxBuffer: MAX_BUFFER,
|
|
19578
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
19579
|
+
killSignal: "SIGKILL",
|
|
19580
|
+
windowsHide: true
|
|
19581
|
+
});
|
|
19582
|
+
const error = result.error;
|
|
19583
|
+
return {
|
|
19584
|
+
ok: result.status === 0,
|
|
19585
|
+
stdout: result.stdout ?? "",
|
|
19586
|
+
stderr: result.stderr ?? "",
|
|
19587
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
19588
|
+
};
|
|
19589
|
+
};
|
|
19590
|
+
var line = (run) => {
|
|
19591
|
+
if (!run.ok)
|
|
19592
|
+
return null;
|
|
19593
|
+
const value = run.stdout.trim();
|
|
19594
|
+
return value ? value : null;
|
|
19595
|
+
};
|
|
19596
|
+
function headSha(cwd) {
|
|
19597
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
19598
|
+
}
|
|
19599
|
+
var emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" }));
|
|
19600
|
+
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
19601
|
+
var seedIndex = (from, to) => {
|
|
19602
|
+
fs9.copyFileSync(from, to);
|
|
19603
|
+
const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
|
|
19604
|
+
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
19605
|
+
fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
19606
|
+
};
|
|
19607
|
+
function worktreeTree(cwd, options = {}) {
|
|
19608
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
19609
|
+
const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
19610
|
+
if (!top || !indexPath)
|
|
19611
|
+
return null;
|
|
19612
|
+
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
19613
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
19614
|
+
const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
|
|
19615
|
+
try {
|
|
19616
|
+
const tempIndex = path11.join(scratch, "index");
|
|
19617
|
+
if (fs9.existsSync(indexPath))
|
|
19618
|
+
seedIndex(indexPath, tempIndex);
|
|
19619
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
19620
|
+
const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
19621
|
+
env,
|
|
19622
|
+
timeoutMs
|
|
19623
|
+
});
|
|
19624
|
+
if (!untracked.ok)
|
|
19625
|
+
return null;
|
|
19626
|
+
const skipped = [];
|
|
19627
|
+
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
19628
|
+
try {
|
|
19629
|
+
const stat = fs9.statSync(path11.join(top, file));
|
|
19630
|
+
if (stat.isFile() && stat.size > cap)
|
|
19631
|
+
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
19632
|
+
} catch {}
|
|
19633
|
+
}
|
|
19634
|
+
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
19635
|
+
const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
19636
|
+
env,
|
|
19637
|
+
timeoutMs,
|
|
19638
|
+
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
19639
|
+
}) : git2(top, ["add", "-A"], { env, timeoutMs });
|
|
19640
|
+
if (!added.ok)
|
|
19641
|
+
return null;
|
|
19642
|
+
const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
|
|
19643
|
+
if (!tree)
|
|
19644
|
+
return null;
|
|
19645
|
+
const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
19646
|
+
const key = skipped.length ? `sha256:${createHash6("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
19647
|
+
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
19648
|
+
} finally {
|
|
19649
|
+
fs9.rmSync(scratch, { recursive: true, force: true });
|
|
19650
|
+
}
|
|
19651
|
+
}
|
|
19652
|
+
function worktreeSignal(cwd, options = {}) {
|
|
19653
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
19654
|
+
if (!top)
|
|
19655
|
+
return null;
|
|
19656
|
+
const status = git2(top, [
|
|
19657
|
+
"status",
|
|
19658
|
+
"--porcelain=v1",
|
|
19659
|
+
"-z",
|
|
19660
|
+
"--untracked-files=all",
|
|
19661
|
+
"--no-renames",
|
|
19662
|
+
"--ignore-submodules=dirty"
|
|
19663
|
+
], {
|
|
19664
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
19665
|
+
timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
|
|
19666
|
+
});
|
|
19667
|
+
if (!status.ok)
|
|
19668
|
+
return null;
|
|
19669
|
+
const hash = createHash6("sha256");
|
|
19670
|
+
hash.update(`${headSha(top) ?? "unborn"}\x00`);
|
|
19671
|
+
for (const entry of status.stdout.split("\x00")) {
|
|
19672
|
+
if (entry.length < 4)
|
|
19673
|
+
continue;
|
|
19674
|
+
const file = entry.slice(3);
|
|
19675
|
+
const absolute = path11.join(top, file);
|
|
19676
|
+
let stat = "-";
|
|
19677
|
+
try {
|
|
19678
|
+
const info = fs9.lstatSync(absolute, { bigint: true });
|
|
19679
|
+
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
19680
|
+
if (info.isDirectory())
|
|
19681
|
+
stat += `:${headSha(absolute) ?? "-"}`;
|
|
19682
|
+
} catch {}
|
|
19683
|
+
hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
|
|
19684
|
+
}
|
|
19685
|
+
return `sig:${hash.digest("hex")}`;
|
|
19686
|
+
}
|
|
19687
|
+
|
|
19326
19688
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
19327
|
-
var digestBytes3 = (value) =>
|
|
19328
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
19329
|
-
var relative = (root, candidate) =>
|
|
19689
|
+
var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex");
|
|
19690
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`);
|
|
19691
|
+
var relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".";
|
|
19330
19692
|
var canonicalPath = (value) => {
|
|
19331
19693
|
if (typeof value !== "string" || !value)
|
|
19332
19694
|
return null;
|
|
19333
19695
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
19334
19696
|
return null;
|
|
19335
|
-
const normalized =
|
|
19697
|
+
const normalized = path12.posix.normalize(value);
|
|
19336
19698
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
19337
19699
|
return null;
|
|
19338
19700
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -19368,7 +19730,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
19368
19730
|
return left.length - right.length;
|
|
19369
19731
|
};
|
|
19370
19732
|
var gitPaths = (root) => {
|
|
19371
|
-
const result =
|
|
19733
|
+
const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19372
19734
|
cwd: root,
|
|
19373
19735
|
encoding: "buffer"
|
|
19374
19736
|
});
|
|
@@ -19380,21 +19742,21 @@ var gitPaths = (root) => {
|
|
|
19380
19742
|
git: !/not a git repository/i.test(stderr)
|
|
19381
19743
|
};
|
|
19382
19744
|
}
|
|
19383
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
19384
|
-
const stagedDeleted =
|
|
19745
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
|
|
19746
|
+
const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
19385
19747
|
if (stagedDeleted.status !== 0) {
|
|
19386
19748
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
19387
19749
|
if (!/not a git repository/i.test(stderr))
|
|
19388
19750
|
return { paths, uncertain: true, git: true };
|
|
19389
19751
|
} else if (stagedDeleted.stdout) {
|
|
19390
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
19752
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
|
|
19391
19753
|
}
|
|
19392
19754
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
19393
19755
|
};
|
|
19394
19756
|
var walk = (root, directory, output) => {
|
|
19395
19757
|
let names;
|
|
19396
19758
|
try {
|
|
19397
|
-
names =
|
|
19759
|
+
names = fs10.readdirSync(directory);
|
|
19398
19760
|
} catch {
|
|
19399
19761
|
return true;
|
|
19400
19762
|
}
|
|
@@ -19402,10 +19764,10 @@ var walk = (root, directory, output) => {
|
|
|
19402
19764
|
for (const name of names) {
|
|
19403
19765
|
if (name === ".git" || name === ".workit")
|
|
19404
19766
|
continue;
|
|
19405
|
-
const target =
|
|
19767
|
+
const target = path12.join(directory, name);
|
|
19406
19768
|
let stat;
|
|
19407
19769
|
try {
|
|
19408
|
-
stat =
|
|
19770
|
+
stat = fs10.lstatSync(target);
|
|
19409
19771
|
} catch {
|
|
19410
19772
|
uncertain = true;
|
|
19411
19773
|
continue;
|
|
@@ -19420,14 +19782,14 @@ var walk = (root, directory, output) => {
|
|
|
19420
19782
|
var scopeRoots = (root, scope) => {
|
|
19421
19783
|
const roots = [];
|
|
19422
19784
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
19423
|
-
const target =
|
|
19785
|
+
const target = path12.resolve(root, item);
|
|
19424
19786
|
if (!inside(root, target))
|
|
19425
19787
|
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` }] });
|
|
19426
19788
|
let ancestor = target;
|
|
19427
|
-
while (!
|
|
19428
|
-
ancestor =
|
|
19789
|
+
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
19790
|
+
ancestor = path12.dirname(ancestor);
|
|
19429
19791
|
try {
|
|
19430
|
-
if (!inside(root,
|
|
19792
|
+
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
19431
19793
|
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` }] });
|
|
19432
19794
|
} catch {
|
|
19433
19795
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -19439,7 +19801,7 @@ var scopeRoots = (root, scope) => {
|
|
|
19439
19801
|
function captureCandidate(root, scope, environment = []) {
|
|
19440
19802
|
let checkout;
|
|
19441
19803
|
try {
|
|
19442
|
-
checkout =
|
|
19804
|
+
checkout = fs10.realpathSync(root);
|
|
19443
19805
|
} catch {
|
|
19444
19806
|
return failure2("invalid_input", "candidate root does not exist");
|
|
19445
19807
|
}
|
|
@@ -19457,11 +19819,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19457
19819
|
if (item === ".git" || item === ".workit")
|
|
19458
19820
|
continue;
|
|
19459
19821
|
names.add(item);
|
|
19460
|
-
if (!
|
|
19822
|
+
if (!fs10.existsSync(target))
|
|
19461
19823
|
continue;
|
|
19462
19824
|
let stat;
|
|
19463
19825
|
try {
|
|
19464
|
-
stat =
|
|
19826
|
+
stat = fs10.lstatSync(target);
|
|
19465
19827
|
} catch {
|
|
19466
19828
|
uncertain = true;
|
|
19467
19829
|
continue;
|
|
@@ -19477,17 +19839,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19477
19839
|
for (const item of [...names].toSorted()) {
|
|
19478
19840
|
if (!scopeMatches(item, normalizedScope))
|
|
19479
19841
|
continue;
|
|
19480
|
-
const target =
|
|
19842
|
+
const target = path12.join(checkout, item);
|
|
19481
19843
|
try {
|
|
19482
|
-
const stat =
|
|
19844
|
+
const stat = fs10.lstatSync(target);
|
|
19483
19845
|
if (stat.isSymbolicLink()) {
|
|
19484
|
-
const link =
|
|
19846
|
+
const link = fs10.readlinkSync(target);
|
|
19485
19847
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
19486
19848
|
} else if (stat.isFile()) {
|
|
19487
19849
|
files.push({
|
|
19488
19850
|
path: item,
|
|
19489
19851
|
kind: "file",
|
|
19490
|
-
digest: digestBytes3(
|
|
19852
|
+
digest: digestBytes3(fs10.readFileSync(target)),
|
|
19491
19853
|
executable: (stat.mode & 73) !== 0
|
|
19492
19854
|
});
|
|
19493
19855
|
} else if (!stat.isDirectory()) {
|
|
@@ -19512,7 +19874,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19512
19874
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
19513
19875
|
if (values.some(({ value }) => value === null))
|
|
19514
19876
|
completeness = "uncertain";
|
|
19515
|
-
const headResult =
|
|
19877
|
+
const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
19516
19878
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
19517
19879
|
completeness = "uncertain";
|
|
19518
19880
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -19550,7 +19912,24 @@ var sameSession = (left, right) => {
|
|
|
19550
19912
|
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
19551
19913
|
};
|
|
19552
19914
|
var evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope);
|
|
19553
|
-
|
|
19915
|
+
var memo2 = (compute) => {
|
|
19916
|
+
let value;
|
|
19917
|
+
return () => {
|
|
19918
|
+
if (value === undefined)
|
|
19919
|
+
value = compute();
|
|
19920
|
+
return value;
|
|
19921
|
+
};
|
|
19922
|
+
};
|
|
19923
|
+
var treeFreshness = (root) => ({
|
|
19924
|
+
mode: "tree",
|
|
19925
|
+
current: memo2(() => worktreeTree(root)?.key ?? null)
|
|
19926
|
+
});
|
|
19927
|
+
var signalFreshness = (root) => ({
|
|
19928
|
+
mode: "signal",
|
|
19929
|
+
current: memo2(() => worktreeSignal(root))
|
|
19930
|
+
});
|
|
19931
|
+
var isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli";
|
|
19932
|
+
function evaluateEvidence(task, candidate, freshness) {
|
|
19554
19933
|
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
19555
19934
|
return task.evidence.map((entry) => {
|
|
19556
19935
|
const evidence = entry.data;
|
|
@@ -19560,6 +19939,25 @@ function evaluateEvidence(task, candidate) {
|
|
|
19560
19939
|
status: "failed",
|
|
19561
19940
|
reason: "failed evidence remains historical"
|
|
19562
19941
|
};
|
|
19942
|
+
const observation = isObservedCheck(entry) ? evidence.observation : undefined;
|
|
19943
|
+
if (observation && observation.tree !== null) {
|
|
19944
|
+
if (task.status === "closed")
|
|
19945
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
|
|
19946
|
+
if (observation.modifiedWorktree)
|
|
19947
|
+
return {
|
|
19948
|
+
evidenceId: entry.id,
|
|
19949
|
+
status: "stale",
|
|
19950
|
+
reason: "the check changed the worktree while it ran; ignore or clean its outputs"
|
|
19951
|
+
};
|
|
19952
|
+
const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
|
|
19953
|
+
if (recorded !== null && freshness && recorded === freshness.current())
|
|
19954
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
|
|
19955
|
+
return {
|
|
19956
|
+
evidenceId: entry.id,
|
|
19957
|
+
status: "stale",
|
|
19958
|
+
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"
|
|
19959
|
+
};
|
|
19960
|
+
}
|
|
19563
19961
|
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
19564
19962
|
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
19565
19963
|
return {
|
|
@@ -19643,8 +20041,45 @@ var expectedKinds = (dimension, ruleId) => {
|
|
|
19643
20041
|
return "decision";
|
|
19644
20042
|
return "worker report";
|
|
19645
20043
|
};
|
|
19646
|
-
|
|
19647
|
-
|
|
20044
|
+
var observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
|
|
20045
|
+
function evaluateObservedGate(task, requirement, statuses, config) {
|
|
20046
|
+
const names = gateCheckNames(config, requirement.dimension);
|
|
20047
|
+
const observed = task.evidence.filter(isObservedCheck);
|
|
20048
|
+
const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
|
|
20049
|
+
const named = (entry) => {
|
|
20050
|
+
const observation = entry.data.observation;
|
|
20051
|
+
return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
|
|
20052
|
+
};
|
|
20053
|
+
const considered = observed.map((entry) => entry.id);
|
|
20054
|
+
const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
|
|
20055
|
+
if (config.error)
|
|
20056
|
+
return {
|
|
20057
|
+
satisfied: [],
|
|
20058
|
+
considered,
|
|
20059
|
+
reason: `check config is invalid (${config.error}); fix it, then run ${run}`
|
|
20060
|
+
};
|
|
20061
|
+
if (!names.length)
|
|
20062
|
+
return {
|
|
20063
|
+
satisfied: [],
|
|
20064
|
+
considered,
|
|
20065
|
+
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())}`
|
|
20066
|
+
};
|
|
20067
|
+
const satisfied = fresh.filter(named);
|
|
20068
|
+
if (satisfied.length)
|
|
20069
|
+
return {
|
|
20070
|
+
satisfied: satisfied.map((entry) => entry.id),
|
|
20071
|
+
considered,
|
|
20072
|
+
reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
|
|
20073
|
+
};
|
|
20074
|
+
const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
|
|
20075
|
+
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}`;
|
|
20076
|
+
return { satisfied: [], considered, reason };
|
|
20077
|
+
}
|
|
20078
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
|
|
20079
|
+
const evidence = evaluateEvidence(task, candidate, freshness);
|
|
20080
|
+
const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
|
|
20081
|
+
let checkConfig;
|
|
20082
|
+
const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
|
|
19648
20083
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
19649
20084
|
return task.policy.requirements.map((requirement) => ({
|
|
19650
20085
|
requirementId: requirement.id,
|
|
@@ -19654,8 +20089,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
19654
20089
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
19655
20090
|
}));
|
|
19656
20091
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
20092
|
+
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
20093
|
+
if (gate?.satisfied.length)
|
|
20094
|
+
return {
|
|
20095
|
+
requirementId: requirement.id,
|
|
20096
|
+
status: "satisfied",
|
|
20097
|
+
evidenceIds: gate.satisfied,
|
|
20098
|
+
decisionIds: [],
|
|
20099
|
+
reason: gate.reason
|
|
20100
|
+
};
|
|
19657
20101
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
19658
20102
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
20103
|
+
if (gate)
|
|
20104
|
+
return false;
|
|
19659
20105
|
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
19660
20106
|
return false;
|
|
19661
20107
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
@@ -19718,6 +20164,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
19718
20164
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
19719
20165
|
};
|
|
19720
20166
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
20167
|
+
if (gate)
|
|
20168
|
+
return {
|
|
20169
|
+
requirementId: requirement.id,
|
|
20170
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
20171
|
+
evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
|
|
20172
|
+
decisionIds: [],
|
|
20173
|
+
reason: unavailable ? "required capability is unavailable" : gate.reason
|
|
20174
|
+
};
|
|
19721
20175
|
if (!unavailable && related.length > 0 && !passed.length)
|
|
19722
20176
|
return {
|
|
19723
20177
|
requirementId: requirement.id,
|
|
@@ -19870,7 +20324,7 @@ function reconcileResume(view, observations = []) {
|
|
|
19870
20324
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
19871
20325
|
if (!candidate.ok)
|
|
19872
20326
|
return candidate;
|
|
19873
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
20327
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
19874
20328
|
const blockers = [...view.task.progress.blockers];
|
|
19875
20329
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
19876
20330
|
blockers.push({
|
|
@@ -21210,10 +21664,7 @@ class WorkitCore {
|
|
|
21210
21664
|
if (finding.data.disposition !== "fixed")
|
|
21211
21665
|
return finding;
|
|
21212
21666
|
const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
|
|
21213
|
-
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
|
|
21214
|
-
item.evidenceId,
|
|
21215
|
-
item.status
|
|
21216
|
-
]));
|
|
21667
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
21217
21668
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
21218
21669
|
kind: item.data.kind,
|
|
21219
21670
|
candidateId: item.data.candidateId,
|
|
@@ -21228,6 +21679,71 @@ class WorkitCore {
|
|
|
21228
21679
|
return changed;
|
|
21229
21680
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
21230
21681
|
}
|
|
21682
|
+
observeCheck(request) {
|
|
21683
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
21684
|
+
}
|
|
21685
|
+
observeCheckOnce(request) {
|
|
21686
|
+
const root = this.contextRootError();
|
|
21687
|
+
if (!root.ok)
|
|
21688
|
+
return root;
|
|
21689
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
21690
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
21691
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
21692
|
+
if (!observation.success)
|
|
21693
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
21694
|
+
fields: observation.error.issues.map((issue) => ({
|
|
21695
|
+
path: issue.path.join("."),
|
|
21696
|
+
reason: issue.message
|
|
21697
|
+
}))
|
|
21698
|
+
});
|
|
21699
|
+
const task = this.store.readTask(request.taskId);
|
|
21700
|
+
if (!task.ok)
|
|
21701
|
+
return task;
|
|
21702
|
+
if (task.data.status === "closed")
|
|
21703
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
21704
|
+
const observed = observation.data;
|
|
21705
|
+
let candidate = null;
|
|
21706
|
+
if (observed.tree === null) {
|
|
21707
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
21708
|
+
if (!captured.ok)
|
|
21709
|
+
return captured;
|
|
21710
|
+
candidate = captured.data;
|
|
21711
|
+
}
|
|
21712
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
21713
|
+
const data = {
|
|
21714
|
+
kind: "check",
|
|
21715
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
21716
|
+
requirementIds: [],
|
|
21717
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
21718
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
21719
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
21720
|
+
refs: [],
|
|
21721
|
+
exitCode: observed.exitCode,
|
|
21722
|
+
reviewContext: null,
|
|
21723
|
+
observation: observed
|
|
21724
|
+
};
|
|
21725
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
21726
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
21727
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
21728
|
+
return success(mutation.revision, null, {
|
|
21729
|
+
...current,
|
|
21730
|
+
critical,
|
|
21731
|
+
candidates,
|
|
21732
|
+
evidence: [
|
|
21733
|
+
...current.evidence,
|
|
21734
|
+
{
|
|
21735
|
+
id: newId(),
|
|
21736
|
+
recordedAt: mutation.now,
|
|
21737
|
+
provenance: provenance(this.context, "host_observed"),
|
|
21738
|
+
data
|
|
21739
|
+
}
|
|
21740
|
+
]
|
|
21741
|
+
});
|
|
21742
|
+
}, trustedNow(this.context));
|
|
21743
|
+
if (!changed.ok)
|
|
21744
|
+
return changed;
|
|
21745
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
21746
|
+
}
|
|
21231
21747
|
decision(request) {
|
|
21232
21748
|
return this.recordDecision(request);
|
|
21233
21749
|
}
|
|
@@ -21463,7 +21979,7 @@ class WorkitCore {
|
|
|
21463
21979
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
21464
21980
|
if (!current.ok)
|
|
21465
21981
|
return current;
|
|
21466
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
21982
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
21467
21983
|
const verified = evidence.some((entry) => {
|
|
21468
21984
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
21469
21985
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -22167,7 +22683,8 @@ class WorkitCore {
|
|
|
22167
22683
|
if (!current.ok)
|
|
22168
22684
|
return current;
|
|
22169
22685
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
22170
|
-
const
|
|
22686
|
+
const tree = treeFreshness(this.store.root);
|
|
22687
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
22171
22688
|
return success(task.revision, workspace.data.revision, {
|
|
22172
22689
|
id: task.id,
|
|
22173
22690
|
revision: task.revision,
|
|
@@ -22184,7 +22701,7 @@ class WorkitCore {
|
|
|
22184
22701
|
writer: evaluationWorkspace.writer
|
|
22185
22702
|
});
|
|
22186
22703
|
}
|
|
22187
|
-
compactContext(taskId) {
|
|
22704
|
+
compactContext(taskId, freshness) {
|
|
22188
22705
|
const root = this.contextRootError();
|
|
22189
22706
|
if (!root.ok)
|
|
22190
22707
|
return root;
|
|
@@ -22194,12 +22711,12 @@ class WorkitCore {
|
|
|
22194
22711
|
const helper = this.helperTaskGuard(task.data, true);
|
|
22195
22712
|
if (!helper.ok)
|
|
22196
22713
|
return helper;
|
|
22197
|
-
const view = this.view(task.data, false);
|
|
22714
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
22198
22715
|
if (!view.ok)
|
|
22199
22716
|
return view;
|
|
22200
22717
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
22201
22718
|
}
|
|
22202
|
-
view(task, capture = true) {
|
|
22719
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
22203
22720
|
const workspace = this.store.readWorkspace();
|
|
22204
22721
|
if (!workspace.ok)
|
|
22205
22722
|
return workspace;
|
|
@@ -22210,13 +22727,14 @@ class WorkitCore {
|
|
|
22210
22727
|
if (!current.ok)
|
|
22211
22728
|
return current;
|
|
22212
22729
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
22213
|
-
const
|
|
22730
|
+
const tree = freshness;
|
|
22731
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
22214
22732
|
return success(task.revision, workspace.data.revision, {
|
|
22215
22733
|
task,
|
|
22216
22734
|
workspace: evaluationWorkspace,
|
|
22217
22735
|
capabilities: this.context.capabilities,
|
|
22218
22736
|
requirements,
|
|
22219
|
-
evidence: evaluateEvidence(task, current.data)
|
|
22737
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
22220
22738
|
});
|
|
22221
22739
|
}
|
|
22222
22740
|
reconcileResume(view, observations = []) {
|
|
@@ -22443,13 +22961,14 @@ class WorkitCore {
|
|
|
22443
22961
|
return current;
|
|
22444
22962
|
const withCandidate = current.data;
|
|
22445
22963
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
22446
|
-
const
|
|
22964
|
+
const tree = treeFreshness(this.store.root);
|
|
22965
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
22447
22966
|
const view = {
|
|
22448
22967
|
task: taskForView,
|
|
22449
22968
|
workspace: workspace.data,
|
|
22450
22969
|
capabilities: this.context.capabilities,
|
|
22451
22970
|
requirements,
|
|
22452
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
22971
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
22453
22972
|
};
|
|
22454
22973
|
const closure = evaluateClosure(input.outcome, view);
|
|
22455
22974
|
if (!closure.ok)
|
|
@@ -22506,22 +23025,22 @@ function resolveGitRevision(root, value) {
|
|
|
22506
23025
|
}
|
|
22507
23026
|
function resolveInside(root, candidate) {
|
|
22508
23027
|
const base = realpathSync7(root);
|
|
22509
|
-
const target =
|
|
22510
|
-
if (target !== base && !target.startsWith(base +
|
|
23028
|
+
const target = path13.resolve(base, candidate);
|
|
23029
|
+
if (target !== base && !target.startsWith(base + path13.sep)) {
|
|
22511
23030
|
throw new Error("path must stay inside repository root");
|
|
22512
23031
|
}
|
|
22513
23032
|
let ancestor = target;
|
|
22514
23033
|
while (!existsSync5(ancestor))
|
|
22515
|
-
ancestor =
|
|
23034
|
+
ancestor = path13.dirname(ancestor);
|
|
22516
23035
|
const canonicalAncestor = realpathSync7(ancestor);
|
|
22517
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
23036
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path13.sep)) {
|
|
22518
23037
|
throw new Error("path must stay inside repository root");
|
|
22519
23038
|
}
|
|
22520
23039
|
return target;
|
|
22521
23040
|
}
|
|
22522
23041
|
function run2(root, executable, args, env = {}) {
|
|
22523
23042
|
const cwd = realpathSync7(root);
|
|
22524
|
-
const result =
|
|
23043
|
+
const result = spawnSync6(executable, args, {
|
|
22525
23044
|
cwd,
|
|
22526
23045
|
encoding: "utf8",
|
|
22527
23046
|
env: { ...process.env, ...env }
|
|
@@ -22540,12 +23059,13 @@ var SUPPORT_MATRIX = {
|
|
|
22540
23059
|
node: { minimum: "24", current: "24.20.0" },
|
|
22541
23060
|
opencode: { minimum: "1.18.30", current: "1.18.34" },
|
|
22542
23061
|
codex: { cli: "0.153.4", desktopPackage: "26.901.20858", bundledCodexCli: "0.153.0-alpha.5" },
|
|
23062
|
+
claudeCode: { cli: "2.1.288" },
|
|
22543
23063
|
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
|
22544
23064
|
};
|
|
22545
23065
|
|
|
22546
23066
|
// packages/workit-mcp/src/server.ts
|
|
22547
23067
|
import { existsSync as existsSync7, readFileSync as readFileSync8, realpathSync as realpathSync8 } from "node:fs";
|
|
22548
|
-
import
|
|
23068
|
+
import path17 from "node:path";
|
|
22549
23069
|
// node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
|
|
22550
23070
|
function isZ4Schema(s) {
|
|
22551
23071
|
const schema = s;
|
|
@@ -24924,7 +25444,7 @@ class StdioServerTransport {
|
|
|
24924
25444
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
24925
25445
|
|
|
24926
25446
|
// packages/workit-core/src/core/logger.ts
|
|
24927
|
-
import
|
|
25447
|
+
import os3 from "node:os";
|
|
24928
25448
|
var REDACTED = "[REDACTED]";
|
|
24929
25449
|
var SECRET_VALUE = /\b(?:Bearer|Basic|Digest|Token)\s+\S+/gi;
|
|
24930
25450
|
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;
|
|
@@ -24967,7 +25487,7 @@ var SENSITIVE_WORDS = new Set([
|
|
|
24967
25487
|
]);
|
|
24968
25488
|
var STACK_WORDS = new Set(["stack", "stacktrace", "trace"]);
|
|
24969
25489
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24970
|
-
var homePattern = new RegExp(`${escapeRegExp(
|
|
25490
|
+
var homePattern = new RegExp(`${escapeRegExp(os3.homedir())}(?=[/\\\\]|$)|\\$HOME(?=[/\\\\]|$)`, "g");
|
|
24971
25491
|
var applyPatterns = (value) => {
|
|
24972
25492
|
let out = value;
|
|
24973
25493
|
out = out.replace(SECRET_VALUE, REDACTED);
|
|
@@ -24981,9 +25501,9 @@ var applyPatterns = (value) => {
|
|
|
24981
25501
|
var redactSecrets = (value) => applyPatterns(value);
|
|
24982
25502
|
|
|
24983
25503
|
// packages/workit-core/src/core/pr-create.ts
|
|
24984
|
-
import
|
|
24985
|
-
import
|
|
24986
|
-
import { spawnSync as
|
|
25504
|
+
import fs11 from "node:fs";
|
|
25505
|
+
import path14 from "node:path";
|
|
25506
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
24987
25507
|
// packages/workit-core/src/forge/pr-body.ts
|
|
24988
25508
|
function parseGhIssue(value) {
|
|
24989
25509
|
const m = /issues\/(\d+)/.exec(value);
|
|
@@ -24993,13 +25513,13 @@ function parseGhIssue(value) {
|
|
|
24993
25513
|
// packages/workit-core/src/core/pr-create.ts
|
|
24994
25514
|
function whichOnPath(tool) {
|
|
24995
25515
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
24996
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
25516
|
+
for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
|
|
24997
25517
|
if (!dir)
|
|
24998
25518
|
continue;
|
|
24999
25519
|
for (const name of names) {
|
|
25000
|
-
const candidate =
|
|
25520
|
+
const candidate = path14.join(dir, name);
|
|
25001
25521
|
try {
|
|
25002
|
-
|
|
25522
|
+
fs11.accessSync(candidate, fs11.constants.X_OK);
|
|
25003
25523
|
return candidate;
|
|
25004
25524
|
} catch {}
|
|
25005
25525
|
}
|
|
@@ -25008,7 +25528,7 @@ function whichOnPath(tool) {
|
|
|
25008
25528
|
}
|
|
25009
25529
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
25010
25530
|
var pushRemote = (cwd) => {
|
|
25011
|
-
const result =
|
|
25531
|
+
const result = spawnSync7("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
25012
25532
|
cwd,
|
|
25013
25533
|
encoding: "utf8"
|
|
25014
25534
|
});
|
|
@@ -25019,9 +25539,9 @@ var pushRemote = (cwd) => {
|
|
|
25019
25539
|
};
|
|
25020
25540
|
|
|
25021
25541
|
// packages/workit-core/src/core/repo-context.ts
|
|
25022
|
-
import { spawnSync as
|
|
25542
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
25023
25543
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
25024
|
-
import
|
|
25544
|
+
import path15 from "node:path";
|
|
25025
25545
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
25026
25546
|
try {
|
|
25027
25547
|
gitRevisionParts(value);
|
|
@@ -25048,7 +25568,7 @@ var invalidRange = (cwd) => ({
|
|
|
25048
25568
|
cwd
|
|
25049
25569
|
});
|
|
25050
25570
|
var runGit = (cwd, args) => {
|
|
25051
|
-
const result =
|
|
25571
|
+
const result = spawnSync8("git", args, { cwd, encoding: "utf8" });
|
|
25052
25572
|
return {
|
|
25053
25573
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
25054
25574
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -25155,7 +25675,7 @@ var findPrTemplate = (cwd) => {
|
|
|
25155
25675
|
]) {
|
|
25156
25676
|
const parts = rel.split("/");
|
|
25157
25677
|
const base = parts[parts.length - 1];
|
|
25158
|
-
const dir = parts.length > 1 ?
|
|
25678
|
+
const dir = parts.length > 1 ? path15.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
25159
25679
|
const actual = probeCaseInsensitive(dir, base);
|
|
25160
25680
|
if (actual)
|
|
25161
25681
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -25186,7 +25706,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
25186
25706
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
25187
25707
|
};
|
|
25188
25708
|
var packageScripts = (cwd, keys) => {
|
|
25189
|
-
const pkgPath =
|
|
25709
|
+
const pkgPath = path15.join(cwd, "package.json");
|
|
25190
25710
|
if (!existsSync6(pkgPath))
|
|
25191
25711
|
return [];
|
|
25192
25712
|
try {
|
|
@@ -25212,9 +25732,9 @@ var documentationFiles = (cwd) => {
|
|
|
25212
25732
|
if (entry.isDirectory()) {
|
|
25213
25733
|
if (excluded.has(entry.name))
|
|
25214
25734
|
continue;
|
|
25215
|
-
walk(
|
|
25735
|
+
walk(path15.join(dir, entry.name), depth + 1);
|
|
25216
25736
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
25217
|
-
matches.push("./" +
|
|
25737
|
+
matches.push("./" + path15.relative(cwd, path15.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
25218
25738
|
}
|
|
25219
25739
|
}
|
|
25220
25740
|
};
|
|
@@ -25230,7 +25750,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
25230
25750
|
`);
|
|
25231
25751
|
};
|
|
25232
25752
|
function prReadyContext(root, range) {
|
|
25233
|
-
const cwd =
|
|
25753
|
+
const cwd = path15.resolve(repoRoot(root));
|
|
25234
25754
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25235
25755
|
return invalidRange(cwd);
|
|
25236
25756
|
let stdout = "";
|
|
@@ -25288,7 +25808,7 @@ ${status.stderr}` : "");
|
|
|
25288
25808
|
stdout += `template_path: ${template}
|
|
25289
25809
|
|
|
25290
25810
|
`;
|
|
25291
|
-
stdout += readTrimmed2(
|
|
25811
|
+
stdout += readTrimmed2(path15.join(cwd, template), 220);
|
|
25292
25812
|
} else {
|
|
25293
25813
|
stdout += `template_path: none
|
|
25294
25814
|
|
|
@@ -25298,7 +25818,7 @@ ${status.stderr}` : "");
|
|
|
25298
25818
|
stdout += `
|
|
25299
25819
|
`;
|
|
25300
25820
|
stdout += printSection("Recent Validation Signals");
|
|
25301
|
-
if (existsSync6(
|
|
25821
|
+
if (existsSync6(path15.join(cwd, "package.json"))) {
|
|
25302
25822
|
stdout += `package.json detected. Common scripts:
|
|
25303
25823
|
`;
|
|
25304
25824
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -25307,7 +25827,7 @@ ${status.stderr}` : "");
|
|
|
25307
25827
|
`) + `
|
|
25308
25828
|
`;
|
|
25309
25829
|
}
|
|
25310
|
-
if (existsSync6(
|
|
25830
|
+
if (existsSync6(path15.join(cwd, "Cargo.toml")) || existsSync6(path15.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
25311
25831
|
stdout += `Rust project detected.
|
|
25312
25832
|
`;
|
|
25313
25833
|
}
|
|
@@ -25340,7 +25860,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
25340
25860
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
25341
25861
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
25342
25862
|
function changelogContext(root, range) {
|
|
25343
|
-
const cwd =
|
|
25863
|
+
const cwd = path15.resolve(repoRoot(root));
|
|
25344
25864
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25345
25865
|
return invalidRange(cwd);
|
|
25346
25866
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -25358,7 +25878,7 @@ function changelogContext(root, range) {
|
|
|
25358
25878
|
stdout += CHANGELOG_RULES + `
|
|
25359
25879
|
`;
|
|
25360
25880
|
stdout += printSection("Existing CHANGELOG.md");
|
|
25361
|
-
const changelogPath =
|
|
25881
|
+
const changelogPath = path15.join(cwd, "CHANGELOG.md");
|
|
25362
25882
|
if (existsSync6(changelogPath)) {
|
|
25363
25883
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
25364
25884
|
`;
|
|
@@ -25378,7 +25898,7 @@ function changelogContext(root, range) {
|
|
|
25378
25898
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
25379
25899
|
}
|
|
25380
25900
|
function docsRefreshContext(root, range) {
|
|
25381
|
-
const cwd =
|
|
25901
|
+
const cwd = path15.resolve(repoRoot(root));
|
|
25382
25902
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
25383
25903
|
return invalidRange(cwd);
|
|
25384
25904
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -25400,7 +25920,7 @@ function docsRefreshContext(root, range) {
|
|
|
25400
25920
|
`) + `
|
|
25401
25921
|
`;
|
|
25402
25922
|
stdout += printSection("README Preview");
|
|
25403
|
-
const readme =
|
|
25923
|
+
const readme = path15.join(cwd, "README.md");
|
|
25404
25924
|
if (existsSync6(readme)) {
|
|
25405
25925
|
stdout += readTrimmed2(readme, 220) + `
|
|
25406
25926
|
`;
|
|
@@ -25409,7 +25929,7 @@ function docsRefreshContext(root, range) {
|
|
|
25409
25929
|
`;
|
|
25410
25930
|
}
|
|
25411
25931
|
stdout += printSection("Package Scripts");
|
|
25412
|
-
const pkgPath =
|
|
25932
|
+
const pkgPath = path15.join(cwd, "package.json");
|
|
25413
25933
|
if (existsSync6(pkgPath)) {
|
|
25414
25934
|
try {
|
|
25415
25935
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
|
|
@@ -25423,7 +25943,7 @@ function docsRefreshContext(root, range) {
|
|
|
25423
25943
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
25424
25944
|
}
|
|
25425
25945
|
function releaseNotesContext(root, rangeOrTag) {
|
|
25426
|
-
const cwd =
|
|
25946
|
+
const cwd = path15.resolve(repoRoot(root));
|
|
25427
25947
|
if (!rangeOrTag) {
|
|
25428
25948
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
25429
25949
|
`, exitCode: 1, cwd };
|
|
@@ -25461,7 +25981,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25461
25981
|
`;
|
|
25462
25982
|
stdout += printSection("Existing Release Files");
|
|
25463
25983
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
25464
|
-
if (existsSync6(
|
|
25984
|
+
if (existsSync6(path15.join(cwd, rel)))
|
|
25465
25985
|
stdout += rel + `
|
|
25466
25986
|
`;
|
|
25467
25987
|
}
|
|
@@ -25489,7 +26009,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25489
26009
|
}
|
|
25490
26010
|
|
|
25491
26011
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
25492
|
-
import { spawnSync as
|
|
26012
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
25493
26013
|
var repoPathFromRemote = (remote) => {
|
|
25494
26014
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
25495
26015
|
if (!rest)
|
|
@@ -25510,7 +26030,7 @@ var defaultCli = (bin, available) => {
|
|
|
25510
26030
|
if (!available)
|
|
25511
26031
|
return null;
|
|
25512
26032
|
return (args, root) => {
|
|
25513
|
-
const out =
|
|
26033
|
+
const out = spawnSync9(bin, args, {
|
|
25514
26034
|
cwd: root,
|
|
25515
26035
|
encoding: "utf8",
|
|
25516
26036
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25588,8 +26108,8 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25588
26108
|
};
|
|
25589
26109
|
|
|
25590
26110
|
// packages/workit-core/src/core/youtrack.ts
|
|
25591
|
-
import
|
|
25592
|
-
import
|
|
26111
|
+
import fs12 from "node:fs";
|
|
26112
|
+
import path16 from "node:path";
|
|
25593
26113
|
|
|
25594
26114
|
// packages/workit-core/src/core/templates.ts
|
|
25595
26115
|
var repoRoot2 = assetRoot();
|
|
@@ -25602,22 +26122,22 @@ var resolveWorkspaceRoot = (explicit) => explicit || process.cwd();
|
|
|
25602
26122
|
// packages/workit-core/src/core/youtrack.ts
|
|
25603
26123
|
var ISSUE_RE = /^[A-Z]+-\d+$/;
|
|
25604
26124
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
25605
|
-
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
25606
|
-
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ??
|
|
26125
|
+
var youTrackConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path16.join(configDir(), "youtrack.json");
|
|
26126
|
+
var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? path16.join(resolveConfigDir(), "youtrack.json");
|
|
25607
26127
|
var youTrackTokenModeOk = (p) => {
|
|
25608
26128
|
if (process.platform === "win32")
|
|
25609
26129
|
return true;
|
|
25610
|
-
const mode =
|
|
26130
|
+
const mode = fs12.statSync(p).mode & 511;
|
|
25611
26131
|
return mode === 384;
|
|
25612
26132
|
};
|
|
25613
26133
|
function readYouTrackConfig(required) {
|
|
25614
26134
|
const cfgPath = youTrackConfigPath();
|
|
25615
|
-
if (!
|
|
26135
|
+
if (!fs12.existsSync(cfgPath)) {
|
|
25616
26136
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25617
26137
|
}
|
|
25618
26138
|
let parsed;
|
|
25619
26139
|
try {
|
|
25620
|
-
parsed = JSON.parse(
|
|
26140
|
+
parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
|
|
25621
26141
|
} catch {
|
|
25622
26142
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25623
26143
|
}
|
|
@@ -25628,11 +26148,11 @@ function readYouTrackConfig(required) {
|
|
|
25628
26148
|
}
|
|
25629
26149
|
var readYouTrackContextConfig = () => {
|
|
25630
26150
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25631
|
-
if (!
|
|
26151
|
+
if (!fs12.existsSync(cfgPath))
|
|
25632
26152
|
return { data: {} };
|
|
25633
26153
|
let parsed;
|
|
25634
26154
|
try {
|
|
25635
|
-
parsed = JSON.parse(
|
|
26155
|
+
parsed = JSON.parse(fs12.readFileSync(cfgPath, "utf8"));
|
|
25636
26156
|
} catch {
|
|
25637
26157
|
return { error: "YouTrack configuration is unavailable" };
|
|
25638
26158
|
}
|
|
@@ -25671,12 +26191,12 @@ var youTrackToken = () => {
|
|
|
25671
26191
|
if ("error" in loaded)
|
|
25672
26192
|
return loaded;
|
|
25673
26193
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25674
|
-
const tokenPath = tokenFile ?
|
|
25675
|
-
if (!tokenPath || !
|
|
26194
|
+
const tokenPath = tokenFile ? path16.isAbsolute(tokenFile) ? path16.resolve(tokenFile) : path16.resolve(process.cwd(), tokenFile) : "";
|
|
26195
|
+
if (!tokenPath || !fs12.existsSync(tokenPath))
|
|
25676
26196
|
return { error: "missing youtrack.token" };
|
|
25677
26197
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25678
26198
|
return { error: "youtrack.token mode must be 0600" };
|
|
25679
|
-
const token =
|
|
26199
|
+
const token = fs12.readFileSync(tokenPath, "utf8").trim();
|
|
25680
26200
|
if (!token)
|
|
25681
26201
|
return { error: "empty token file" };
|
|
25682
26202
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25741,9 +26261,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25741
26261
|
} catch {
|
|
25742
26262
|
continue;
|
|
25743
26263
|
}
|
|
25744
|
-
if (!
|
|
26264
|
+
if (!fs12.existsSync(full))
|
|
25745
26265
|
continue;
|
|
25746
|
-
const text =
|
|
26266
|
+
const text = fs12.readFileSync(full, "utf8");
|
|
25747
26267
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25748
26268
|
if (m)
|
|
25749
26269
|
return m[1];
|
|
@@ -25896,10 +26416,6 @@ var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\
|
|
|
25896
26416
|
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
25897
26417
|
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
25898
26418
|
|
|
25899
|
-
// packages/workit-core/src/git/rev.ts
|
|
25900
|
-
var MAX_BUFFER = 256 * 1024 * 1024;
|
|
25901
|
-
var MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
25902
|
-
|
|
25903
26419
|
// packages/workit-core/src/ledger.ts
|
|
25904
26420
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
25905
26421
|
var FAILING = new Set(["failed", "blocked"]);
|
|
@@ -26041,13 +26557,13 @@ var rootVariants = (workspaceRoot) => {
|
|
|
26041
26557
|
if (!workspaceRoot)
|
|
26042
26558
|
return [];
|
|
26043
26559
|
const roots = new Set;
|
|
26044
|
-
for (const candidate of [workspaceRoot,
|
|
26045
|
-
if (
|
|
26560
|
+
for (const candidate of [workspaceRoot, path17.resolve(workspaceRoot)]) {
|
|
26561
|
+
if (path17.isAbsolute(candidate) && candidate !== path17.parse(candidate).root)
|
|
26046
26562
|
roots.add(candidate);
|
|
26047
26563
|
}
|
|
26048
26564
|
try {
|
|
26049
26565
|
const canonical = realpathSync8(workspaceRoot);
|
|
26050
|
-
if (canonical !==
|
|
26566
|
+
if (canonical !== path17.parse(canonical).root)
|
|
26051
26567
|
roots.add(canonical);
|
|
26052
26568
|
} catch {}
|
|
26053
26569
|
return [...roots].toSorted((left, right) => right.length - left.length);
|
|
@@ -26148,11 +26664,11 @@ var thrownResult = (tool, error, workspaceRoot) => {
|
|
|
26148
26664
|
};
|
|
26149
26665
|
function createMcpServer(host, contextProvider) {
|
|
26150
26666
|
assertMcpHost(host);
|
|
26151
|
-
const here =
|
|
26667
|
+
const here = path17.dirname(fileURLToPath2(import.meta.url));
|
|
26152
26668
|
const sourceMarker = markSourcesLoaded([
|
|
26153
|
-
|
|
26154
|
-
|
|
26155
|
-
|
|
26669
|
+
path17.join(here, "server.ts"),
|
|
26670
|
+
path17.join(here, "..", "..", "workit-core", "src", "core", "task-contract.ts"),
|
|
26671
|
+
path17.join(here, "..", "..", "workit-core", "src", "core", "task-engine.ts")
|
|
26156
26672
|
].filter((file) => existsSync7(file)));
|
|
26157
26673
|
let staleWarned = false;
|
|
26158
26674
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
@@ -26342,6 +26858,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
26342
26858
|
};
|
|
26343
26859
|
});
|
|
26344
26860
|
// packages/workit-core/src/hooks/context.ts
|
|
26861
|
+
var signals = new Map;
|
|
26345
26862
|
var cache = new Map;
|
|
26346
26863
|
// packages/workit-core/src/hooks/handle.ts
|
|
26347
26864
|
var offered = new Set;
|
|
@@ -26489,8 +27006,8 @@ var codexQualification = (host) => host === "codex_cli" ? { surface: host, cli:
|
|
|
26489
27006
|
var resolveCodexWorkspaceRoot = (pluginRoot, env) => {
|
|
26490
27007
|
const explicit = env.WORKFLOW_WORKSPACE_ROOT;
|
|
26491
27008
|
const inherited = env.PWD;
|
|
26492
|
-
const candidate = explicit ?? (inherited &&
|
|
26493
|
-
if (!candidate || !
|
|
27009
|
+
const candidate = explicit ?? (inherited && path18.resolve(inherited) !== path18.resolve(pluginRoot) ? inherited : undefined);
|
|
27010
|
+
if (!candidate || !path18.isAbsolute(candidate) || !existsSync8(candidate))
|
|
26494
27011
|
return null;
|
|
26495
27012
|
try {
|
|
26496
27013
|
if (!statSync3(candidate).isDirectory())
|