@brainervirus/workit-cursor 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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +605 -70
- package/dist/mcp-server.js +628 -112
- package/dist/workit-hook.js +612 -67
- package/package.json +3 -3
- package/skills/workit-behavioral-tdd/SKILL.md +15 -9
- package/skills/workit-debug/SKILL.md +2 -1
|
@@ -7825,7 +7825,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
|
|
|
7825
7825
|
}, rewriteRecordRefs = (value, schema, map) => {
|
|
7826
7826
|
const rewritten = rewriteNode(value, schema, map);
|
|
7827
7827
|
return rewritten.drop ? null : rewritten.value;
|
|
7828
|
-
}, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0), strippable = (issues, prefix = []) => {
|
|
7828
|
+
}, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, checkObservationSchema, CHECK_OBSERVATION_PATH = "evidence.*.data.observation", evidenceFields, evidenceSchema, reportedEvidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0), strippable = (issues, prefix = []) => {
|
|
7829
7829
|
const strips = [];
|
|
7830
7830
|
for (const issue of issues) {
|
|
7831
7831
|
if (issue.code === "unrecognized_keys") {
|
|
@@ -8157,7 +8157,36 @@ var init_task_contract = __esm(() => {
|
|
|
8157
8157
|
path: ["id"]
|
|
8158
8158
|
});
|
|
8159
8159
|
});
|
|
8160
|
-
|
|
8160
|
+
checkObservationSchema = object({
|
|
8161
|
+
observer: literal("workit_cli"),
|
|
8162
|
+
name: nonEmpty.nullable(),
|
|
8163
|
+
configured: boolean2(),
|
|
8164
|
+
argv: array(text).min(1),
|
|
8165
|
+
shell: boolean2(),
|
|
8166
|
+
cwd: nonEmpty,
|
|
8167
|
+
exitCode: safeInteger,
|
|
8168
|
+
durationMs: safeInteger.min(0),
|
|
8169
|
+
timedOut: boolean2(),
|
|
8170
|
+
head: text.nullable(),
|
|
8171
|
+
tree: text.nullable(),
|
|
8172
|
+
dirty: boolean2().nullable(),
|
|
8173
|
+
signal: text.nullable(),
|
|
8174
|
+
treeAfter: text.nullable(),
|
|
8175
|
+
modifiedWorktree: boolean2(),
|
|
8176
|
+
base: text.nullable(),
|
|
8177
|
+
patchId: text.nullable(),
|
|
8178
|
+
environment: object({
|
|
8179
|
+
platform: nonEmpty,
|
|
8180
|
+
arch: nonEmpty,
|
|
8181
|
+
vars: record(nonEmpty, text.nullable())
|
|
8182
|
+
}).strict(),
|
|
8183
|
+
logDigest: text.nullable(),
|
|
8184
|
+
logRef: text.nullable(),
|
|
8185
|
+
logTail: array(text).max(40),
|
|
8186
|
+
ledgerRowId: text.nullable(),
|
|
8187
|
+
attestation: object({ host: nonEmpty, session: text.nullable(), agentId: text.nullable() }).strict().nullable()
|
|
8188
|
+
}).strict();
|
|
8189
|
+
evidenceFields = {
|
|
8161
8190
|
kind: _enum(["check", "review", "investigation", "artifact"]),
|
|
8162
8191
|
claim: text,
|
|
8163
8192
|
requirementIds: array(digest),
|
|
@@ -8168,7 +8197,9 @@ var init_task_contract = __esm(() => {
|
|
|
8168
8197
|
refs: array(refSchema),
|
|
8169
8198
|
exitCode: safeInteger.nullable(),
|
|
8170
8199
|
reviewContext: refSchema.nullable()
|
|
8171
|
-
}
|
|
8200
|
+
};
|
|
8201
|
+
evidenceSchema = object({ ...evidenceFields, observation: checkObservationSchema.optional() }).strict();
|
|
8202
|
+
reportedEvidenceSchema = object(evidenceFields).strict();
|
|
8172
8203
|
evidenceEvaluationSchema = object({
|
|
8173
8204
|
evidenceId: id,
|
|
8174
8205
|
status: _enum(["passed", "failed", "missing", "skipped", "stale"]),
|
|
@@ -8425,7 +8456,7 @@ var init_task_contract = __esm(() => {
|
|
|
8425
8456
|
action: literal("record"),
|
|
8426
8457
|
...taskId,
|
|
8427
8458
|
expectedRevision: revision.optional(),
|
|
8428
|
-
evidence:
|
|
8459
|
+
evidence: reportedEvidenceSchema
|
|
8429
8460
|
})
|
|
8430
8461
|
};
|
|
8431
8462
|
findingOperations = {
|
|
@@ -12500,15 +12531,340 @@ var init_policy_resolver = __esm(() => {
|
|
|
12500
12531
|
}).strict();
|
|
12501
12532
|
});
|
|
12502
12533
|
|
|
12503
|
-
// packages/workit-core/src/
|
|
12504
|
-
import { createHash as createHash7 } from "node:crypto";
|
|
12505
|
-
import * as fs8 from "node:fs";
|
|
12534
|
+
// packages/workit-core/src/check-config.ts
|
|
12506
12535
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
12536
|
+
import fs8 from "node:fs";
|
|
12507
12537
|
import path10 from "node:path";
|
|
12538
|
+
function splitCommand(command) {
|
|
12539
|
+
const argv = [];
|
|
12540
|
+
let current = "";
|
|
12541
|
+
let quote = null;
|
|
12542
|
+
let started = false;
|
|
12543
|
+
for (const char of command) {
|
|
12544
|
+
if (quote) {
|
|
12545
|
+
if (char === quote)
|
|
12546
|
+
quote = null;
|
|
12547
|
+
else
|
|
12548
|
+
current += char;
|
|
12549
|
+
continue;
|
|
12550
|
+
}
|
|
12551
|
+
if (char === '"' || char === "'") {
|
|
12552
|
+
quote = char;
|
|
12553
|
+
started = true;
|
|
12554
|
+
continue;
|
|
12555
|
+
}
|
|
12556
|
+
if (/\s/u.test(char)) {
|
|
12557
|
+
if (started)
|
|
12558
|
+
argv.push(current);
|
|
12559
|
+
current = "";
|
|
12560
|
+
started = false;
|
|
12561
|
+
continue;
|
|
12562
|
+
}
|
|
12563
|
+
if (SHELL_META.test(char))
|
|
12564
|
+
return new Error(`"${command}" uses shell syntax (${char}); configure an argv array instead`);
|
|
12565
|
+
current += char;
|
|
12566
|
+
started = true;
|
|
12567
|
+
}
|
|
12568
|
+
if (quote)
|
|
12569
|
+
return new Error(`"${command}" has an unterminated quote`);
|
|
12570
|
+
if (started)
|
|
12571
|
+
argv.push(current);
|
|
12572
|
+
return argv.length ? argv : new Error("empty command");
|
|
12573
|
+
}
|
|
12574
|
+
function checkRoot(cwd) {
|
|
12575
|
+
return path10.resolve(git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd);
|
|
12576
|
+
}
|
|
12577
|
+
function detectedChecks(root) {
|
|
12578
|
+
const read = (file) => {
|
|
12579
|
+
try {
|
|
12580
|
+
return fs8.readFileSync(path10.join(root, file), "utf8");
|
|
12581
|
+
} catch {
|
|
12582
|
+
return null;
|
|
12583
|
+
}
|
|
12584
|
+
};
|
|
12585
|
+
const checks = [];
|
|
12586
|
+
const add = (check) => {
|
|
12587
|
+
if (!checks.some((item) => item.name === check.name))
|
|
12588
|
+
checks.push(check);
|
|
12589
|
+
};
|
|
12590
|
+
const manifestText = read("package.json");
|
|
12591
|
+
if (manifestText !== null) {
|
|
12592
|
+
let manifest = null;
|
|
12593
|
+
try {
|
|
12594
|
+
manifest = JSON.parse(manifestText);
|
|
12595
|
+
} catch {}
|
|
12596
|
+
const scripts = isRecord2(manifest) && isRecord2(manifest.scripts) ? manifest.scripts : {};
|
|
12597
|
+
const pm = packageManager(root);
|
|
12598
|
+
for (const name of SCRIPT_CHECKS) {
|
|
12599
|
+
const script = scripts[name];
|
|
12600
|
+
if (typeof script === "string" && script.trim() !== "")
|
|
12601
|
+
add(scriptCheck(pm, name));
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
const plain = (name, argv) => ({ name, argv, accepts: [argv] });
|
|
12605
|
+
if (read("go.mod") !== null)
|
|
12606
|
+
add(plain("test", ["go", "test", "./..."]));
|
|
12607
|
+
if (read("Cargo.toml") !== null)
|
|
12608
|
+
add(plain("test", ["cargo", "test"]));
|
|
12609
|
+
if (read("pytest.ini") !== null || /\bpytest\b/u.test(read("pyproject.toml") ?? ""))
|
|
12610
|
+
add(plain("test", ["pytest"]));
|
|
12611
|
+
const makefile = read("Makefile") ?? read("makefile") ?? read("GNUmakefile");
|
|
12612
|
+
if (makefile !== null && /^test\s*:(?!=)/mu.test(makefile))
|
|
12613
|
+
add(plain("test", ["make", "test"]));
|
|
12614
|
+
return checks;
|
|
12615
|
+
}
|
|
12616
|
+
function loadCheckConfig(cwd) {
|
|
12617
|
+
const root = checkRoot(cwd);
|
|
12618
|
+
const file = path10.join(root, CHECKS_FILE);
|
|
12619
|
+
const none = (error = null) => ({
|
|
12620
|
+
source: error ? CHECKS_FILE : "none",
|
|
12621
|
+
root,
|
|
12622
|
+
checks: [],
|
|
12623
|
+
gates: {},
|
|
12624
|
+
error
|
|
12625
|
+
});
|
|
12626
|
+
let text = null;
|
|
12627
|
+
try {
|
|
12628
|
+
text = fs8.readFileSync(file, "utf8");
|
|
12629
|
+
} catch (error) {
|
|
12630
|
+
if (error.code !== "ENOENT")
|
|
12631
|
+
return none(`cannot read ${CHECKS_FILE}: ${error.message}`);
|
|
12632
|
+
}
|
|
12633
|
+
if (text === null) {
|
|
12634
|
+
const checks = detectedChecks(root);
|
|
12635
|
+
return checks.length ? { source: "defaults", root, checks, gates: {}, error: null } : none();
|
|
12636
|
+
}
|
|
12637
|
+
let parsed;
|
|
12638
|
+
try {
|
|
12639
|
+
parsed = JSON.parse(text);
|
|
12640
|
+
} catch {
|
|
12641
|
+
return none(`${CHECKS_FILE} is not valid JSON`);
|
|
12642
|
+
}
|
|
12643
|
+
if (!isRecord2(parsed) || !isRecord2(parsed.checks))
|
|
12644
|
+
return none(`${CHECKS_FILE} must be {"checks":{"<name>":"<command>"|["argv",…]}}`);
|
|
12645
|
+
const checks = [];
|
|
12646
|
+
for (const [name, value] of Object.entries(parsed.checks)) {
|
|
12647
|
+
if (!CHECK_NAME.test(name))
|
|
12648
|
+
return none(`${CHECKS_FILE}: check name "${name}" must match ${CHECK_NAME.source}`);
|
|
12649
|
+
const argv = typeof value === "string" ? splitCommand(value) : Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && item.length > 0) ? value : new Error("expected a command string or a non-empty argv array");
|
|
12650
|
+
if (argv instanceof Error)
|
|
12651
|
+
return none(`${CHECKS_FILE}: check "${name}": ${argv.message}`);
|
|
12652
|
+
checks.push({ name, argv, accepts: [argv] });
|
|
12653
|
+
}
|
|
12654
|
+
const gates = {};
|
|
12655
|
+
if (parsed.gates !== undefined) {
|
|
12656
|
+
if (!isRecord2(parsed.gates))
|
|
12657
|
+
return none(`${CHECKS_FILE}: gates must be {"testing":"<name>"|["<name>",…]}`);
|
|
12658
|
+
for (const [gate, value] of Object.entries(parsed.gates)) {
|
|
12659
|
+
if (!GATES.includes(gate))
|
|
12660
|
+
return none(`${CHECKS_FILE}: unknown gate "${gate}" (use ${GATES.join(" or ")})`);
|
|
12661
|
+
const names = typeof value === "string" ? [value] : value;
|
|
12662
|
+
if (!Array.isArray(names) || names.length === 0 || !names.every((name) => typeof name === "string" && checks.some((c) => c.name === name)))
|
|
12663
|
+
return none(`${CHECKS_FILE}: gates.${gate} must name configured checks`);
|
|
12664
|
+
gates[gate] = names;
|
|
12665
|
+
}
|
|
12666
|
+
}
|
|
12667
|
+
return { source: CHECKS_FILE, root, checks, gates, error: null };
|
|
12668
|
+
}
|
|
12669
|
+
function matchesNamedCheck(config, name, argv, cwd) {
|
|
12670
|
+
if (config.error || name === null || cwd !== ".")
|
|
12671
|
+
return false;
|
|
12672
|
+
const check = config.checks.find((item) => item.name === name);
|
|
12673
|
+
return Boolean(check?.accepts.some((accepted) => sameArgv(accepted, argv)));
|
|
12674
|
+
}
|
|
12675
|
+
function gateCheckNames(config, dimension) {
|
|
12676
|
+
const explicit = config.gates[dimension];
|
|
12677
|
+
if (explicit)
|
|
12678
|
+
return explicit;
|
|
12679
|
+
const names = config.checks.map((check) => check.name);
|
|
12680
|
+
if (dimension === "testing")
|
|
12681
|
+
return names.includes("test") ? ["test"] : [];
|
|
12682
|
+
return names;
|
|
12683
|
+
}
|
|
12684
|
+
function workitAvailable(env = process.env) {
|
|
12685
|
+
if (workitOnPath !== undefined)
|
|
12686
|
+
return workitOnPath;
|
|
12687
|
+
const windows = process.platform === "win32";
|
|
12688
|
+
const exts = windows ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
12689
|
+
const dirs = (env.PATH ?? env.Path ?? "").split(path10.delimiter).filter(Boolean);
|
|
12690
|
+
workitOnPath = dirs.some((dir) => exts.some((ext) => {
|
|
12691
|
+
try {
|
|
12692
|
+
return fs8.statSync(path10.join(dir, `workit${ext}`)).isFile();
|
|
12693
|
+
} catch {
|
|
12694
|
+
return false;
|
|
12695
|
+
}
|
|
12696
|
+
}));
|
|
12697
|
+
return workitOnPath;
|
|
12698
|
+
}
|
|
12699
|
+
function checkCommand(args, version, onPath = workitAvailable()) {
|
|
12700
|
+
return onPath ? `workit check ${args}` : `npx -y @brainervirus/workit-cli@${version} check ${args}`;
|
|
12701
|
+
}
|
|
12702
|
+
var SCRIPT_CHECKS, CHECKS_FILE = "workit.checks.json", isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), SHELL_META, git = (cwd, args) => {
|
|
12703
|
+
const run = spawnSync3("git", args, {
|
|
12704
|
+
cwd,
|
|
12705
|
+
encoding: "utf8",
|
|
12706
|
+
timeout: 20000,
|
|
12707
|
+
killSignal: "SIGKILL",
|
|
12708
|
+
windowsHide: true
|
|
12709
|
+
});
|
|
12710
|
+
const value = run.status === 0 ? (run.stdout ?? "").trim() : "";
|
|
12711
|
+
return value || null;
|
|
12712
|
+
}, packageManager = (root) => {
|
|
12713
|
+
const has = (file) => fs8.existsSync(path10.join(root, file));
|
|
12714
|
+
if (has("bun.lock") || has("bun.lockb"))
|
|
12715
|
+
return "bun";
|
|
12716
|
+
if (has("pnpm-lock.yaml"))
|
|
12717
|
+
return "pnpm";
|
|
12718
|
+
if (has("yarn.lock"))
|
|
12719
|
+
return "yarn";
|
|
12720
|
+
return "npm";
|
|
12721
|
+
}, scriptCheck = (pm, name) => {
|
|
12722
|
+
const argv = [pm, "run", name];
|
|
12723
|
+
const accepts = [argv];
|
|
12724
|
+
if (pm === "pnpm" || pm === "yarn" || pm === "npm" && name === "test")
|
|
12725
|
+
accepts.push([pm, name]);
|
|
12726
|
+
return { name, argv, accepts };
|
|
12727
|
+
}, CHECK_NAME, GATES, sameArgv = (left, right) => left.length === right.length && left.every((item, index) => item === right[index]), workitOnPath;
|
|
12728
|
+
var init_check_config = __esm(() => {
|
|
12729
|
+
SCRIPT_CHECKS = ["test", "lint", "typecheck", "check"];
|
|
12730
|
+
SHELL_META = /[|&;<>()$`\\*?[\]{}~!#]/u;
|
|
12731
|
+
CHECK_NAME = /^[a-z0-9][a-z0-9:._-]{0,63}$/u;
|
|
12732
|
+
GATES = ["testing", "verification"];
|
|
12733
|
+
});
|
|
12734
|
+
|
|
12735
|
+
// packages/workit-core/src/git/rev.ts
|
|
12736
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
12737
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12738
|
+
import fs9 from "node:fs";
|
|
12739
|
+
import os2 from "node:os";
|
|
12740
|
+
import path11 from "node:path";
|
|
12741
|
+
function headSha(cwd) {
|
|
12742
|
+
return line(git2(cwd, ["rev-parse", "--verify", "-q", "HEAD^{commit}"]));
|
|
12743
|
+
}
|
|
12744
|
+
function worktreeTree(cwd, options = {}) {
|
|
12745
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
12746
|
+
const indexPath = line(git2(cwd, ["rev-parse", "--path-format=absolute", "--git-path", "index"]));
|
|
12747
|
+
if (!top || !indexPath)
|
|
12748
|
+
return null;
|
|
12749
|
+
const cap = options.maxUntrackedBytes ?? MAX_UNTRACKED_BYTES;
|
|
12750
|
+
const timeoutMs = options.timeoutMs ?? GIT_TIMEOUTS.worktree;
|
|
12751
|
+
const scratch = fs9.mkdtempSync(path11.join(os2.tmpdir(), "workit-tree-"));
|
|
12752
|
+
try {
|
|
12753
|
+
const tempIndex = path11.join(scratch, "index");
|
|
12754
|
+
if (fs9.existsSync(indexPath))
|
|
12755
|
+
seedIndex(indexPath, tempIndex);
|
|
12756
|
+
const env = { ...process.env, GIT_INDEX_FILE: tempIndex };
|
|
12757
|
+
const untracked = git2(top, ["ls-files", "--others", "--exclude-standard", "-z"], {
|
|
12758
|
+
env,
|
|
12759
|
+
timeoutMs
|
|
12760
|
+
});
|
|
12761
|
+
if (!untracked.ok)
|
|
12762
|
+
return null;
|
|
12763
|
+
const skipped = [];
|
|
12764
|
+
for (const file of untracked.stdout.split("\x00").filter(Boolean)) {
|
|
12765
|
+
try {
|
|
12766
|
+
const stat = fs9.statSync(path11.join(top, file));
|
|
12767
|
+
if (stat.isFile() && stat.size > cap)
|
|
12768
|
+
skipped.push({ path: file, size: stat.size, mtimeMs: Math.trunc(stat.mtimeMs) });
|
|
12769
|
+
} catch {}
|
|
12770
|
+
}
|
|
12771
|
+
skipped.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
12772
|
+
const added = skipped.length ? git2(top, ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], {
|
|
12773
|
+
env,
|
|
12774
|
+
timeoutMs,
|
|
12775
|
+
input: [".", ...skipped.map((file) => `:(exclude,literal)${file.path}`)].join("\x00")
|
|
12776
|
+
}) : git2(top, ["add", "-A"], { env, timeoutMs });
|
|
12777
|
+
if (!added.ok)
|
|
12778
|
+
return null;
|
|
12779
|
+
const tree = line(git2(top, ["write-tree"], { env, timeoutMs }));
|
|
12780
|
+
if (!tree)
|
|
12781
|
+
return null;
|
|
12782
|
+
const base = line(git2(top, ["rev-parse", "--verify", "-q", "HEAD^{tree}"])) ?? emptyTree(top);
|
|
12783
|
+
const key = skipped.length ? `sha256:${createHash7("sha256").update(JSON.stringify({ tree, skipped })).digest("hex")}` : tree;
|
|
12784
|
+
return { tree, key, dirty: tree !== base || skipped.length > 0, skipped };
|
|
12785
|
+
} finally {
|
|
12786
|
+
fs9.rmSync(scratch, { recursive: true, force: true });
|
|
12787
|
+
}
|
|
12788
|
+
}
|
|
12789
|
+
function worktreeSignal(cwd, options = {}) {
|
|
12790
|
+
const top = line(git2(cwd, ["rev-parse", "--show-toplevel"]));
|
|
12791
|
+
if (!top)
|
|
12792
|
+
return null;
|
|
12793
|
+
const status = git2(top, [
|
|
12794
|
+
"status",
|
|
12795
|
+
"--porcelain=v1",
|
|
12796
|
+
"-z",
|
|
12797
|
+
"--untracked-files=all",
|
|
12798
|
+
"--no-renames",
|
|
12799
|
+
"--ignore-submodules=dirty"
|
|
12800
|
+
], {
|
|
12801
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
12802
|
+
timeoutMs: options.timeoutMs ?? GIT_TIMEOUTS.worktree
|
|
12803
|
+
});
|
|
12804
|
+
if (!status.ok)
|
|
12805
|
+
return null;
|
|
12806
|
+
const hash = createHash7("sha256");
|
|
12807
|
+
hash.update(`${headSha(top) ?? "unborn"}\x00`);
|
|
12808
|
+
for (const entry of status.stdout.split("\x00")) {
|
|
12809
|
+
if (entry.length < 4)
|
|
12810
|
+
continue;
|
|
12811
|
+
const file = entry.slice(3);
|
|
12812
|
+
const absolute = path11.join(top, file);
|
|
12813
|
+
let stat = "-";
|
|
12814
|
+
try {
|
|
12815
|
+
const info = fs9.lstatSync(absolute, { bigint: true });
|
|
12816
|
+
stat = `${info.mode.toString(8)}:${info.size}:${info.mtimeNs}`;
|
|
12817
|
+
if (info.isDirectory())
|
|
12818
|
+
stat += `:${headSha(absolute) ?? "-"}`;
|
|
12819
|
+
} catch {}
|
|
12820
|
+
hash.update(`${entry.slice(0, 2)}\x00${file}\x00${stat}\x00`);
|
|
12821
|
+
}
|
|
12822
|
+
return `sig:${hash.digest("hex")}`;
|
|
12823
|
+
}
|
|
12824
|
+
var MAX_BUFFER, GIT_TIMEOUTS, git2 = (cwd, args, options = {}) => {
|
|
12825
|
+
const result = spawnSync4("git", args, {
|
|
12826
|
+
cwd,
|
|
12827
|
+
encoding: "utf8",
|
|
12828
|
+
env: options.env ?? process.env,
|
|
12829
|
+
input: options.input,
|
|
12830
|
+
maxBuffer: MAX_BUFFER,
|
|
12831
|
+
timeout: options.timeoutMs ?? GIT_TIMEOUTS.local,
|
|
12832
|
+
killSignal: "SIGKILL",
|
|
12833
|
+
windowsHide: true
|
|
12834
|
+
});
|
|
12835
|
+
const error = result.error;
|
|
12836
|
+
return {
|
|
12837
|
+
ok: result.status === 0,
|
|
12838
|
+
stdout: result.stdout ?? "",
|
|
12839
|
+
stderr: result.stderr ?? "",
|
|
12840
|
+
timedOut: error?.code === "ETIMEDOUT"
|
|
12841
|
+
};
|
|
12842
|
+
}, line = (run) => {
|
|
12843
|
+
if (!run.ok)
|
|
12844
|
+
return null;
|
|
12845
|
+
const value = run.stdout.trim();
|
|
12846
|
+
return value ? value : null;
|
|
12847
|
+
}, emptyTree = (cwd) => line(git2(cwd, ["mktree"], { input: "" })), MAX_UNTRACKED_BYTES, seedIndex = (from, to) => {
|
|
12848
|
+
fs9.copyFileSync(from, to);
|
|
12849
|
+
const { atimeNs, mtimeNs } = fs9.statSync(from, { bigint: true });
|
|
12850
|
+
const seconds = (ns) => (Number(ns / 1000n) - 1) / 1e6;
|
|
12851
|
+
fs9.utimesSync(to, seconds(atimeNs), seconds(mtimeNs));
|
|
12852
|
+
};
|
|
12853
|
+
var init_rev = __esm(() => {
|
|
12854
|
+
MAX_BUFFER = 256 * 1024 * 1024;
|
|
12855
|
+
GIT_TIMEOUTS = { local: 20000, worktree: 120000, network: 15000 };
|
|
12856
|
+
MAX_UNTRACKED_BYTES = 5 * 1024 * 1024;
|
|
12857
|
+
});
|
|
12858
|
+
|
|
12859
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
12860
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
12861
|
+
import * as fs10 from "node:fs";
|
|
12862
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
12863
|
+
import path12 from "node:path";
|
|
12508
12864
|
function captureCandidate(root, scope, environment = []) {
|
|
12509
12865
|
let checkout;
|
|
12510
12866
|
try {
|
|
12511
|
-
checkout =
|
|
12867
|
+
checkout = fs10.realpathSync(root);
|
|
12512
12868
|
} catch {
|
|
12513
12869
|
return failure2("invalid_input", "candidate root does not exist");
|
|
12514
12870
|
}
|
|
@@ -12526,11 +12882,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12526
12882
|
if (item === ".git" || item === ".workit")
|
|
12527
12883
|
continue;
|
|
12528
12884
|
names.add(item);
|
|
12529
|
-
if (!
|
|
12885
|
+
if (!fs10.existsSync(target))
|
|
12530
12886
|
continue;
|
|
12531
12887
|
let stat;
|
|
12532
12888
|
try {
|
|
12533
|
-
stat =
|
|
12889
|
+
stat = fs10.lstatSync(target);
|
|
12534
12890
|
} catch {
|
|
12535
12891
|
uncertain = true;
|
|
12536
12892
|
continue;
|
|
@@ -12546,17 +12902,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12546
12902
|
for (const item of [...names].toSorted()) {
|
|
12547
12903
|
if (!scopeMatches(item, normalizedScope))
|
|
12548
12904
|
continue;
|
|
12549
|
-
const target =
|
|
12905
|
+
const target = path12.join(checkout, item);
|
|
12550
12906
|
try {
|
|
12551
|
-
const stat =
|
|
12907
|
+
const stat = fs10.lstatSync(target);
|
|
12552
12908
|
if (stat.isSymbolicLink()) {
|
|
12553
|
-
const link =
|
|
12909
|
+
const link = fs10.readlinkSync(target);
|
|
12554
12910
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
12555
12911
|
} else if (stat.isFile()) {
|
|
12556
12912
|
files.push({
|
|
12557
12913
|
path: item,
|
|
12558
12914
|
kind: "file",
|
|
12559
|
-
digest: digestBytes3(
|
|
12915
|
+
digest: digestBytes3(fs10.readFileSync(target)),
|
|
12560
12916
|
executable: (stat.mode & 73) !== 0
|
|
12561
12917
|
});
|
|
12562
12918
|
} else if (!stat.isDirectory()) {
|
|
@@ -12581,7 +12937,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12581
12937
|
const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
12582
12938
|
if (values.some(({ value }) => value === null))
|
|
12583
12939
|
completeness = "uncertain";
|
|
12584
|
-
const headResult =
|
|
12940
|
+
const headResult = spawnSync5("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
12585
12941
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
12586
12942
|
completeness = "uncertain";
|
|
12587
12943
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -12597,7 +12953,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
12597
12953
|
const parsed = candidateSchema.safeParse(candidate);
|
|
12598
12954
|
return parsed.success ? success(null, null, parsed.data) : failure2("invalid_input", "captured candidate is invalid");
|
|
12599
12955
|
}
|
|
12600
|
-
function evaluateEvidence(task, candidate) {
|
|
12956
|
+
function evaluateEvidence(task, candidate, freshness) {
|
|
12601
12957
|
const current = candidate ?? task.candidates.at(-1) ?? null;
|
|
12602
12958
|
return task.evidence.map((entry) => {
|
|
12603
12959
|
const evidence = entry.data;
|
|
@@ -12607,6 +12963,25 @@ function evaluateEvidence(task, candidate) {
|
|
|
12607
12963
|
status: "failed",
|
|
12608
12964
|
reason: "failed evidence remains historical"
|
|
12609
12965
|
};
|
|
12966
|
+
const observation = isObservedCheck(entry) ? evidence.observation : undefined;
|
|
12967
|
+
if (observation && observation.tree !== null) {
|
|
12968
|
+
if (task.status === "closed")
|
|
12969
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "recorded before close" };
|
|
12970
|
+
if (observation.modifiedWorktree)
|
|
12971
|
+
return {
|
|
12972
|
+
evidenceId: entry.id,
|
|
12973
|
+
status: "stale",
|
|
12974
|
+
reason: "the check changed the worktree while it ran; ignore or clean its outputs"
|
|
12975
|
+
};
|
|
12976
|
+
const recorded = freshness?.mode === "signal" ? observation.signal : observation.tree;
|
|
12977
|
+
if (recorded !== null && freshness && recorded === freshness.current())
|
|
12978
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "worktree is unchanged" };
|
|
12979
|
+
return {
|
|
12980
|
+
evidenceId: entry.id,
|
|
12981
|
+
status: "stale",
|
|
12982
|
+
reason: freshness?.mode === "signal" ? "the worktree may have changed since the check ran (quick signal; inspect re-checks exactly)" : "the worktree changed since the check ran"
|
|
12983
|
+
};
|
|
12984
|
+
}
|
|
12610
12985
|
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
12611
12986
|
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
12612
12987
|
return {
|
|
@@ -12644,8 +13019,44 @@ function evaluateEvidence(task, candidate) {
|
|
|
12644
13019
|
};
|
|
12645
13020
|
});
|
|
12646
13021
|
}
|
|
12647
|
-
function
|
|
12648
|
-
const
|
|
13022
|
+
function evaluateObservedGate(task, requirement, statuses, config) {
|
|
13023
|
+
const names = gateCheckNames(config, requirement.dimension);
|
|
13024
|
+
const observed = task.evidence.filter(isObservedCheck);
|
|
13025
|
+
const fresh = observed.filter((entry) => statuses.get(entry.id) === "passed" && entry.data.observation?.exitCode === 0);
|
|
13026
|
+
const named = (entry) => {
|
|
13027
|
+
const observation = entry.data.observation;
|
|
13028
|
+
return observation.configured && !observation.shell && names.includes(observation.name ?? "") && matchesNamedCheck(config, observation.name, observation.argv, observation.cwd);
|
|
13029
|
+
};
|
|
13030
|
+
const considered = observed.map((entry) => entry.id);
|
|
13031
|
+
const run = checkCommand(names[0] ?? "<name>", runtimeVersion());
|
|
13032
|
+
if (config.error)
|
|
13033
|
+
return {
|
|
13034
|
+
satisfied: [],
|
|
13035
|
+
considered,
|
|
13036
|
+
reason: `check config is invalid (${config.error}); fix it, then run ${run}`
|
|
13037
|
+
};
|
|
13038
|
+
if (!names.length)
|
|
13039
|
+
return {
|
|
13040
|
+
satisfied: [],
|
|
13041
|
+
considered,
|
|
13042
|
+
reason: requirement.dimension === "testing" && config.checks.length ? `no \`test\` check is configured; add one (or gates.testing) to ${CHECKS_FILE}, then run ${checkCommand("test", runtimeVersion())}` : `no checks are configured or detected; add ${CHECKS_FILE} (committed, so it shows in the diff), e.g. {"checks":{"test":"<your test command>"}}, then run ${checkCommand("test", runtimeVersion())}`
|
|
13043
|
+
};
|
|
13044
|
+
const satisfied = fresh.filter(named);
|
|
13045
|
+
if (satisfied.length)
|
|
13046
|
+
return {
|
|
13047
|
+
satisfied: satisfied.map((entry) => entry.id),
|
|
13048
|
+
considered,
|
|
13049
|
+
reason: `a fresh passing configured check (${names.join("|")}) was observed by workit check`
|
|
13050
|
+
};
|
|
13051
|
+
const reported = task.evidence.some((entry) => entry.data.kind === "check" && !isObservedCheck(entry) && entry.data.requirementIds.includes(requirement.id));
|
|
13052
|
+
const reason = fresh.length ? `ad-hoc checks do not satisfy a configured gate; run ${run}` : observed.some((entry) => statuses.get(entry.id) === "stale") ? `observed checks are stale (the worktree changed); run ${run}` : observed.some((entry) => statuses.get(entry.id) === "failed") ? `the observed check failed; fix it and run ${run}` : reported ? `agent-reported checks are notes, not evidence; run ${run}` : `no CLI-observed check (needs kind:check observed by workit check); run ${run}`;
|
|
13053
|
+
return { satisfied: [], considered, reason };
|
|
13054
|
+
}
|
|
13055
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller, freshness = treeFreshness(checkoutRoot)) {
|
|
13056
|
+
const evidence = evaluateEvidence(task, candidate, freshness);
|
|
13057
|
+
const statuses = new Map(evidence.map((item) => [item.evidenceId, item.status]));
|
|
13058
|
+
let checkConfig;
|
|
13059
|
+
const configFor = () => checkConfig ??= loadCheckConfig(checkoutRoot);
|
|
12649
13060
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
12650
13061
|
return task.policy.requirements.map((requirement) => ({
|
|
12651
13062
|
requirementId: requirement.id,
|
|
@@ -12655,8 +13066,19 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
12655
13066
|
reason: "stored policy version is unsupported; reassessment is required"
|
|
12656
13067
|
}));
|
|
12657
13068
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
13069
|
+
const gate = observedGate(requirement) ? evaluateObservedGate(task, requirement, statuses, configFor()) : null;
|
|
13070
|
+
if (gate?.satisfied.length)
|
|
13071
|
+
return {
|
|
13072
|
+
requirementId: requirement.id,
|
|
13073
|
+
status: "satisfied",
|
|
13074
|
+
evidenceIds: gate.satisfied,
|
|
13075
|
+
decisionIds: [],
|
|
13076
|
+
reason: gate.reason
|
|
13077
|
+
};
|
|
12658
13078
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
12659
13079
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
13080
|
+
if (gate)
|
|
13081
|
+
return false;
|
|
12660
13082
|
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
12661
13083
|
return false;
|
|
12662
13084
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
@@ -12719,6 +13141,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
12719
13141
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
12720
13142
|
};
|
|
12721
13143
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
13144
|
+
if (gate)
|
|
13145
|
+
return {
|
|
13146
|
+
requirementId: requirement.id,
|
|
13147
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
13148
|
+
evidenceIds: [...new Set([...gate.considered, ...related.map(({ entry }) => entry.id)])],
|
|
13149
|
+
decisionIds: [],
|
|
13150
|
+
reason: unavailable ? "required capability is unavailable" : gate.reason
|
|
13151
|
+
};
|
|
12722
13152
|
if (!unavailable && related.length > 0 && !passed.length)
|
|
12723
13153
|
return {
|
|
12724
13154
|
requirementId: requirement.id,
|
|
@@ -12817,12 +13247,12 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
12817
13247
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
12818
13248
|
});
|
|
12819
13249
|
}
|
|
12820
|
-
var digestBytes3 = (value) =>
|
|
13250
|
+
var digestBytes3 = (value) => createHash8("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path12.sep}`), relative = (root, candidate) => path12.relative(root, candidate).split(path12.sep).join("/") || ".", canonicalPath = (value) => {
|
|
12821
13251
|
if (typeof value !== "string" || !value)
|
|
12822
13252
|
return null;
|
|
12823
13253
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
12824
13254
|
return null;
|
|
12825
|
-
const normalized =
|
|
13255
|
+
const normalized = path12.posix.normalize(value);
|
|
12826
13256
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
12827
13257
|
return null;
|
|
12828
13258
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -12852,7 +13282,7 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12852
13282
|
}
|
|
12853
13283
|
return left.length - right.length;
|
|
12854
13284
|
}, gitPaths = (root) => {
|
|
12855
|
-
const result =
|
|
13285
|
+
const result = spawnSync5("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
12856
13286
|
cwd: root,
|
|
12857
13287
|
encoding: "buffer"
|
|
12858
13288
|
});
|
|
@@ -12864,20 +13294,20 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12864
13294
|
git: !/not a git repository/i.test(stderr)
|
|
12865
13295
|
};
|
|
12866
13296
|
}
|
|
12867
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
12868
|
-
const stagedDeleted =
|
|
13297
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")) : [];
|
|
13298
|
+
const stagedDeleted = spawnSync5("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
12869
13299
|
if (stagedDeleted.status !== 0) {
|
|
12870
13300
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
12871
13301
|
if (!/not a git repository/i.test(stderr))
|
|
12872
13302
|
return { paths, uncertain: true, git: true };
|
|
12873
13303
|
} else if (stagedDeleted.stdout) {
|
|
12874
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
13304
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path12.sep).join("/")));
|
|
12875
13305
|
}
|
|
12876
13306
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
12877
13307
|
}, walk = (root, directory, output) => {
|
|
12878
13308
|
let names;
|
|
12879
13309
|
try {
|
|
12880
|
-
names =
|
|
13310
|
+
names = fs10.readdirSync(directory);
|
|
12881
13311
|
} catch {
|
|
12882
13312
|
return true;
|
|
12883
13313
|
}
|
|
@@ -12885,10 +13315,10 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12885
13315
|
for (const name of names) {
|
|
12886
13316
|
if (name === ".git" || name === ".workit")
|
|
12887
13317
|
continue;
|
|
12888
|
-
const target =
|
|
13318
|
+
const target = path12.join(directory, name);
|
|
12889
13319
|
let stat;
|
|
12890
13320
|
try {
|
|
12891
|
-
stat =
|
|
13321
|
+
stat = fs10.lstatSync(target);
|
|
12892
13322
|
} catch {
|
|
12893
13323
|
uncertain = true;
|
|
12894
13324
|
continue;
|
|
@@ -12902,14 +13332,14 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12902
13332
|
}, scopeRoots = (root, scope) => {
|
|
12903
13333
|
const roots = [];
|
|
12904
13334
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
12905
|
-
const target =
|
|
13335
|
+
const target = path12.resolve(root, item);
|
|
12906
13336
|
if (!inside(root, target))
|
|
12907
13337
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
12908
13338
|
let ancestor = target;
|
|
12909
|
-
while (!
|
|
12910
|
-
ancestor =
|
|
13339
|
+
while (!fs10.existsSync(ancestor) && ancestor !== root)
|
|
13340
|
+
ancestor = path12.dirname(ancestor);
|
|
12911
13341
|
try {
|
|
12912
|
-
if (!inside(root,
|
|
13342
|
+
if (!inside(root, fs10.realpathSync(ancestor)))
|
|
12913
13343
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
12914
13344
|
} catch {
|
|
12915
13345
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -12934,7 +13364,20 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12934
13364
|
}, sameSession = (left, right) => {
|
|
12935
13365
|
const other = sessionFromRef(right);
|
|
12936
13366
|
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
12937
|
-
}, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope),
|
|
13367
|
+
}, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), memo2 = (compute) => {
|
|
13368
|
+
let value;
|
|
13369
|
+
return () => {
|
|
13370
|
+
if (value === undefined)
|
|
13371
|
+
value = compute();
|
|
13372
|
+
return value;
|
|
13373
|
+
};
|
|
13374
|
+
}, treeFreshness = (root) => ({
|
|
13375
|
+
mode: "tree",
|
|
13376
|
+
current: memo2(() => worktreeTree(root)?.key ?? null)
|
|
13377
|
+
}), signalFreshness = (root) => ({
|
|
13378
|
+
mode: "signal",
|
|
13379
|
+
current: memo2(() => worktreeSignal(root))
|
|
13380
|
+
}), isObservedCheck = (entry) => entry.data.kind === "check" && entry.data.observation?.observer === "workit_cli" && entry.provenance?.kind === "host_observed" && entry.provenance.host === "workit_cli", scopeCovers2, checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
12938
13381
|
const pin = assignment.candidateId ?? null;
|
|
12939
13382
|
if (pin === null)
|
|
12940
13383
|
return true;
|
|
@@ -12972,10 +13415,13 @@ var digestBytes3 = (value) => createHash7("sha256").update(value).digest("hex"),
|
|
|
12972
13415
|
if (dimension === "decisions")
|
|
12973
13416
|
return "decision";
|
|
12974
13417
|
return "worker report";
|
|
12975
|
-
};
|
|
13418
|
+
}, observedGate = (requirement) => (requirement.dimension === "testing" || requirement.dimension === "verification") && requirement.before === "close";
|
|
12976
13419
|
var init_task_evaluation = __esm(() => {
|
|
12977
13420
|
init_task_contract();
|
|
12978
13421
|
init_authority();
|
|
13422
|
+
init_check_config();
|
|
13423
|
+
init_rev();
|
|
13424
|
+
init_task_store();
|
|
12979
13425
|
scopeCovers2 = scopeCovers;
|
|
12980
13426
|
});
|
|
12981
13427
|
|
|
@@ -13044,8 +13490,11 @@ policy, and reapplies the call, returning busy under persistent contention. A
|
|
|
13044
13490
|
revision_conflict means a revision you passed is stale: re-read the record
|
|
13045
13491
|
before deciding whether to retry.
|
|
13046
13492
|
A solo edit does not need writer acquisition; use it when concurrent checkout
|
|
13047
|
-
writers need coordination. Record only observed facts and checks.
|
|
13048
|
-
|
|
13493
|
+
writers need coordination. Record only observed facts and checks. Close-time
|
|
13494
|
+
testing and verification gates accept only a configured check the CLI ran:
|
|
13495
|
+
\`workit check <name>\` (\`npx -y @brainervirus/workit-cli check <name>\` when
|
|
13496
|
+
\`workit\` is not on PATH); a recorded check result is a note and an ad-hoc
|
|
13497
|
+
\`workit check -- <cmd>\` never satisfies a gate. Evidence can become stale when its bound candidate or tree changes; reconcile findings against the
|
|
13049
13498
|
current candidate before recording completion.
|
|
13050
13499
|
|
|
13051
13500
|
Workit validates domain policy against the actual action target, configured
|
|
@@ -13118,7 +13567,7 @@ function reconcileResume(view, observations = []) {
|
|
|
13118
13567
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
13119
13568
|
if (!candidate.ok)
|
|
13120
13569
|
return candidate;
|
|
13121
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13570
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13122
13571
|
const blockers = [...view.task.progress.blockers];
|
|
13123
13572
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13124
13573
|
blockers.push({
|
|
@@ -13608,10 +14057,7 @@ class WorkitCore {
|
|
|
13608
14057
|
if (finding.data.disposition !== "fixed")
|
|
13609
14058
|
return finding;
|
|
13610
14059
|
const withEntry = { ...current, candidates, evidence: [...current.evidence, entry] };
|
|
13611
|
-
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data).map((item) => [
|
|
13612
|
-
item.evidenceId,
|
|
13613
|
-
item.status
|
|
13614
|
-
]));
|
|
14060
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
13615
14061
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
13616
14062
|
kind: item.data.kind,
|
|
13617
14063
|
candidateId: item.data.candidateId,
|
|
@@ -13626,6 +14072,71 @@ class WorkitCore {
|
|
|
13626
14072
|
return changed;
|
|
13627
14073
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
13628
14074
|
}
|
|
14075
|
+
observeCheck(request) {
|
|
14076
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
14077
|
+
}
|
|
14078
|
+
observeCheckOnce(request) {
|
|
14079
|
+
const root = this.contextRootError();
|
|
14080
|
+
if (!root.ok)
|
|
14081
|
+
return root;
|
|
14082
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
14083
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
14084
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
14085
|
+
if (!observation.success)
|
|
14086
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
14087
|
+
fields: observation.error.issues.map((issue) => ({
|
|
14088
|
+
path: issue.path.join("."),
|
|
14089
|
+
reason: issue.message
|
|
14090
|
+
}))
|
|
14091
|
+
});
|
|
14092
|
+
const task = this.store.readTask(request.taskId);
|
|
14093
|
+
if (!task.ok)
|
|
14094
|
+
return task;
|
|
14095
|
+
if (task.data.status === "closed")
|
|
14096
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
14097
|
+
const observed = observation.data;
|
|
14098
|
+
let candidate = null;
|
|
14099
|
+
if (observed.tree === null) {
|
|
14100
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
14101
|
+
if (!captured.ok)
|
|
14102
|
+
return captured;
|
|
14103
|
+
candidate = captured.data;
|
|
14104
|
+
}
|
|
14105
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
14106
|
+
const data = {
|
|
14107
|
+
kind: "check",
|
|
14108
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
14109
|
+
requirementIds: [],
|
|
14110
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
14111
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
14112
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
14113
|
+
refs: [],
|
|
14114
|
+
exitCode: observed.exitCode,
|
|
14115
|
+
reviewContext: null,
|
|
14116
|
+
observation: observed
|
|
14117
|
+
};
|
|
14118
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
14119
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
14120
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
14121
|
+
return success(mutation.revision, null, {
|
|
14122
|
+
...current,
|
|
14123
|
+
critical,
|
|
14124
|
+
candidates,
|
|
14125
|
+
evidence: [
|
|
14126
|
+
...current.evidence,
|
|
14127
|
+
{
|
|
14128
|
+
id: newId(),
|
|
14129
|
+
recordedAt: mutation.now,
|
|
14130
|
+
provenance: provenance(this.context, "host_observed"),
|
|
14131
|
+
data
|
|
14132
|
+
}
|
|
14133
|
+
]
|
|
14134
|
+
});
|
|
14135
|
+
}, trustedNow(this.context));
|
|
14136
|
+
if (!changed.ok)
|
|
14137
|
+
return changed;
|
|
14138
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
14139
|
+
}
|
|
13629
14140
|
decision(request) {
|
|
13630
14141
|
return this.recordDecision(request);
|
|
13631
14142
|
}
|
|
@@ -13861,7 +14372,7 @@ class WorkitCore {
|
|
|
13861
14372
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
13862
14373
|
if (!current.ok)
|
|
13863
14374
|
return current;
|
|
13864
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
14375
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
13865
14376
|
const verified = evidence.some((entry) => {
|
|
13866
14377
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
13867
14378
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -14565,7 +15076,8 @@ class WorkitCore {
|
|
|
14565
15076
|
if (!current.ok)
|
|
14566
15077
|
return current;
|
|
14567
15078
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
14568
|
-
const
|
|
15079
|
+
const tree = treeFreshness(this.store.root);
|
|
15080
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
14569
15081
|
return success(task.revision, workspace.data.revision, {
|
|
14570
15082
|
id: task.id,
|
|
14571
15083
|
revision: task.revision,
|
|
@@ -14582,7 +15094,7 @@ class WorkitCore {
|
|
|
14582
15094
|
writer: evaluationWorkspace.writer
|
|
14583
15095
|
});
|
|
14584
15096
|
}
|
|
14585
|
-
compactContext(taskId) {
|
|
15097
|
+
compactContext(taskId, freshness) {
|
|
14586
15098
|
const root = this.contextRootError();
|
|
14587
15099
|
if (!root.ok)
|
|
14588
15100
|
return root;
|
|
@@ -14592,12 +15104,12 @@ class WorkitCore {
|
|
|
14592
15104
|
const helper = this.helperTaskGuard(task.data, true);
|
|
14593
15105
|
if (!helper.ok)
|
|
14594
15106
|
return helper;
|
|
14595
|
-
const view = this.view(task.data, false);
|
|
15107
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
14596
15108
|
if (!view.ok)
|
|
14597
15109
|
return view;
|
|
14598
15110
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
14599
15111
|
}
|
|
14600
|
-
view(task, capture = true) {
|
|
15112
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
14601
15113
|
const workspace = this.store.readWorkspace();
|
|
14602
15114
|
if (!workspace.ok)
|
|
14603
15115
|
return workspace;
|
|
@@ -14608,13 +15120,14 @@ class WorkitCore {
|
|
|
14608
15120
|
if (!current.ok)
|
|
14609
15121
|
return current;
|
|
14610
15122
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
14611
|
-
const
|
|
15123
|
+
const tree = freshness;
|
|
15124
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
14612
15125
|
return success(task.revision, workspace.data.revision, {
|
|
14613
15126
|
task,
|
|
14614
15127
|
workspace: evaluationWorkspace,
|
|
14615
15128
|
capabilities: this.context.capabilities,
|
|
14616
15129
|
requirements,
|
|
14617
|
-
evidence: evaluateEvidence(task, current.data)
|
|
15130
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
14618
15131
|
});
|
|
14619
15132
|
}
|
|
14620
15133
|
reconcileResume(view, observations = []) {
|
|
@@ -14841,13 +15354,14 @@ class WorkitCore {
|
|
|
14841
15354
|
return current;
|
|
14842
15355
|
const withCandidate = current.data;
|
|
14843
15356
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
14844
|
-
const
|
|
15357
|
+
const tree = treeFreshness(this.store.root);
|
|
15358
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
14845
15359
|
const view = {
|
|
14846
15360
|
task: taskForView,
|
|
14847
15361
|
workspace: workspace.data,
|
|
14848
15362
|
capabilities: this.context.capabilities,
|
|
14849
15363
|
requirements,
|
|
14850
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
15364
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
14851
15365
|
};
|
|
14852
15366
|
const closure = evaluateClosure(input.outcome, view);
|
|
14853
15367
|
if (!closure.ok)
|
|
@@ -15297,7 +15811,7 @@ var support = (descriptor, axis) => {
|
|
|
15297
15811
|
});
|
|
15298
15812
|
|
|
15299
15813
|
// packages/workit-core/src/hooks/context.ts
|
|
15300
|
-
import
|
|
15814
|
+
import path13 from "node:path";
|
|
15301
15815
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
15302
15816
|
const listed = store.listTaskIndex();
|
|
15303
15817
|
if (!listed.ok)
|
|
@@ -15320,18 +15834,26 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
15320
15834
|
capabilities: context.capabilities
|
|
15321
15835
|
});
|
|
15322
15836
|
const hit = cache.get(slot);
|
|
15323
|
-
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
|
|
15837
|
+
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
15324
15838
|
return hit.value;
|
|
15325
15839
|
const task = store.readTask(entry.id);
|
|
15326
15840
|
if (!task.ok)
|
|
15327
15841
|
return null;
|
|
15328
|
-
const
|
|
15329
|
-
const observed =
|
|
15330
|
-
const
|
|
15842
|
+
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
15843
|
+
const observed = task.data.evidence.some(isObservedCheck);
|
|
15844
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path13.resolve(store.root, ref.path)] : []));
|
|
15845
|
+
const observedFiles = filesKey(files);
|
|
15846
|
+
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
15331
15847
|
if (!compact.ok)
|
|
15332
15848
|
return null;
|
|
15333
15849
|
cache.delete(slot);
|
|
15334
|
-
cache.set(slot, {
|
|
15850
|
+
cache.set(slot, {
|
|
15851
|
+
key,
|
|
15852
|
+
files,
|
|
15853
|
+
filesKey: observedFiles,
|
|
15854
|
+
signal: observed ? freshness.current() : null,
|
|
15855
|
+
value: compact.data
|
|
15856
|
+
});
|
|
15335
15857
|
if (cache.size > CACHE_LIMIT)
|
|
15336
15858
|
cache.delete(cache.keys().next().value);
|
|
15337
15859
|
return compact.data;
|
|
@@ -15352,7 +15874,17 @@ ${tasks.map((task) => `- ${task.id} [${task.status}; source ${task.source.host}/
|
|
|
15352
15874
|
}, filesKey = (files) => {
|
|
15353
15875
|
const signatures = files.map((file) => fileSignature(file) ?? "-");
|
|
15354
15876
|
return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
|
|
15355
|
-
}, CACHE_LIMIT = 64,
|
|
15877
|
+
}, CACHE_LIMIT = 64, SIGNAL_TTL_MS = 1500, signals, recentSignal = (root) => {
|
|
15878
|
+
const now = Date.now();
|
|
15879
|
+
const cached = signals.get(root);
|
|
15880
|
+
if (cached && now - cached.at < SIGNAL_TTL_MS)
|
|
15881
|
+
return cached.value;
|
|
15882
|
+
const value = worktreeSignal(root);
|
|
15883
|
+
signals.set(root, { at: now, value });
|
|
15884
|
+
if (signals.size > CACHE_LIMIT)
|
|
15885
|
+
signals.delete(signals.keys().next().value);
|
|
15886
|
+
return value;
|
|
15887
|
+
}, cache, utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), hookOperationContext = (input, descriptor) => ({
|
|
15356
15888
|
root: input.cwd,
|
|
15357
15889
|
caller: { host: input.host, actor: input.session.id },
|
|
15358
15890
|
callerAttested: false,
|
|
@@ -15395,8 +15927,11 @@ ${options.addendum}` : ""}
|
|
|
15395
15927
|
var init_context = __esm(() => {
|
|
15396
15928
|
init_methods();
|
|
15397
15929
|
init_task_contract();
|
|
15930
|
+
init_task_evaluation();
|
|
15931
|
+
init_rev();
|
|
15398
15932
|
init_task_engine();
|
|
15399
15933
|
init_task_store();
|
|
15934
|
+
signals = new Map;
|
|
15400
15935
|
cache = new Map;
|
|
15401
15936
|
});
|
|
15402
15937
|
|
|
@@ -15504,9 +16039,9 @@ var init_handle = __esm(() => {
|
|
|
15504
16039
|
|
|
15505
16040
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
15506
16041
|
import { existsSync as existsSync5, realpathSync as realpathSync7, statSync as statSync2 } from "node:fs";
|
|
15507
|
-
import
|
|
15508
|
-
var
|
|
15509
|
-
if (!nonEmpty2(value) || !
|
|
16042
|
+
import path14 from "node:path";
|
|
16043
|
+
var isRecord3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value), nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "", optionalText = (value) => typeof value === "string" ? value : null, existingDirectory = (value) => {
|
|
16044
|
+
if (!nonEmpty2(value) || !path14.isAbsolute(value) || !existsSync5(value))
|
|
15510
16045
|
return null;
|
|
15511
16046
|
try {
|
|
15512
16047
|
return statSync2(value).isDirectory() ? realpathSync7(value) : null;
|
|
@@ -15538,7 +16073,7 @@ var init_fields = __esm(() => {
|
|
|
15538
16073
|
});
|
|
15539
16074
|
|
|
15540
16075
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
15541
|
-
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) =>
|
|
16076
|
+
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
|
|
15542
16077
|
const toolUseId = optionalText(value.tool_use_id);
|
|
15543
16078
|
switch (name) {
|
|
15544
16079
|
case "SessionStart":
|
|
@@ -15557,7 +16092,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
|
|
|
15557
16092
|
const command = toolCommand(value);
|
|
15558
16093
|
if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
|
|
15559
16094
|
return { ok: false, error: "only shell PostToolUse is mapped" };
|
|
15560
|
-
const response =
|
|
16095
|
+
const response = isRecord3(value.tool_response) ? value.tool_response : {};
|
|
15561
16096
|
return {
|
|
15562
16097
|
ok: true,
|
|
15563
16098
|
event: {
|
|
@@ -15739,14 +16274,14 @@ var init_claude_code = __esm(() => {
|
|
|
15739
16274
|
claudeCodeAdapter = {
|
|
15740
16275
|
descriptor: CLAUDE_CODE_DESCRIPTOR,
|
|
15741
16276
|
parse(raw) {
|
|
15742
|
-
const native =
|
|
16277
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
15743
16278
|
const fail = (error) => ({
|
|
15744
16279
|
ok: false,
|
|
15745
16280
|
error,
|
|
15746
16281
|
native,
|
|
15747
16282
|
event: native ? EVENTS[native] : null
|
|
15748
16283
|
});
|
|
15749
|
-
if (!
|
|
16284
|
+
if (!isRecord3(raw) || !native)
|
|
15750
16285
|
return fail("hook_event_name is required");
|
|
15751
16286
|
if (!nonEmpty2(raw.session_id))
|
|
15752
16287
|
return fail("session_id is required");
|
|
@@ -15783,7 +16318,7 @@ function detectCodexSurface(env) {
|
|
|
15783
16318
|
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
|
|
15784
16319
|
}
|
|
15785
16320
|
var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase()), SOURCES2, parseCodexHookInput = (value) => {
|
|
15786
|
-
if (!
|
|
16321
|
+
if (!isRecord3(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
|
|
15787
16322
|
return { ok: false, error: "hook_event_name is required" };
|
|
15788
16323
|
const event = value.hook_event_name;
|
|
15789
16324
|
const cwd = existingDirectory(value.cwd);
|
|
@@ -15791,7 +16326,7 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
|
|
|
15791
16326
|
return { ok: false, error: "cwd must be an existing absolute directory" };
|
|
15792
16327
|
if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
|
|
15793
16328
|
return { ok: false, error: "tool_name is required" };
|
|
15794
|
-
const command =
|
|
16329
|
+
const command = isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
15795
16330
|
if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
|
|
15796
16331
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
15797
16332
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
@@ -15985,7 +16520,7 @@ var init_codex = __esm(() => {
|
|
|
15985
16520
|
const host = detectCodexSurface(env);
|
|
15986
16521
|
const parsed = parseCodexHookInput(raw);
|
|
15987
16522
|
if (!parsed.ok) {
|
|
15988
|
-
const native =
|
|
16523
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
15989
16524
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
|
|
15990
16525
|
}
|
|
15991
16526
|
const input = parsed.data;
|
|
@@ -16013,9 +16548,9 @@ var init_codex = __esm(() => {
|
|
|
16013
16548
|
});
|
|
16014
16549
|
|
|
16015
16550
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
16016
|
-
import
|
|
16551
|
+
import path15 from "node:path";
|
|
16017
16552
|
var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
16018
|
-
if (!
|
|
16553
|
+
if (!isRecord3(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
16019
16554
|
return { ok: false, error: "hook_event_name is required" };
|
|
16020
16555
|
const roots = value.workspace_roots;
|
|
16021
16556
|
if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
|
|
@@ -16077,7 +16612,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
|
|
|
16077
16612
|
case "preCompact":
|
|
16078
16613
|
return { kind: "compact.pre", trigger: "auto" };
|
|
16079
16614
|
}
|
|
16080
|
-
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
16615
|
+
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path15.sep) ? root : `${root}${path15.sep}`), hookCwd = (input) => {
|
|
16081
16616
|
const cwd = existingDirectory(input.cwd);
|
|
16082
16617
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
16083
16618
|
return cwd;
|
|
@@ -16229,7 +16764,7 @@ var init_cursor = __esm(() => {
|
|
|
16229
16764
|
parse(raw) {
|
|
16230
16765
|
const parsed = parseCursorHookInput(raw);
|
|
16231
16766
|
if (!parsed.ok) {
|
|
16232
|
-
const native =
|
|
16767
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
16233
16768
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
|
|
16234
16769
|
}
|
|
16235
16770
|
const input = parsed.data;
|