@brainervirus/workit-cursor 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/commands/wk-bdd.md +7 -0
- package/commands/wk-test-audit.md +7 -0
- package/dist/cursor-session-start.js +608 -72
- package/dist/mcp-server.js +628 -112
- package/dist/workit-hook.js +615 -69
- package/package.json +3 -3
- package/skills/workit-bdd/SKILL.md +42 -0
- package/skills/workit-behavioral-tdd/SKILL.md +17 -9
- package/skills/workit-debug/SKILL.md +2 -1
- package/skills/workit-test-audit/SKILL.md +43 -0
|
@@ -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
|
|
@@ -13094,8 +13543,9 @@ target result; a local commit alone is not evidence of a requested remote push.
|
|
|
13094
13543
|
Skill routing: slash aliases /wk-* load on demand. Use workit-steer for a
|
|
13095
13544
|
substantial interruption or change of direction, workit-deslop when relevant
|
|
13096
13545
|
to a PR-ready endpoint, workit-green-run for failing CI, workit-blast-radius
|
|
13097
|
-
when impact is uncertain,
|
|
13098
|
-
choices
|
|
13546
|
+
when impact is uncertain, workit-challenge for genuinely open consequential
|
|
13547
|
+
choices, workit-bdd to turn acceptance criteria into Given/When/Then tests, and
|
|
13548
|
+
workit-test-audit to check tests for tautologies. Load workit-plan when dependencies or handoff need durable next actions.
|
|
13099
13549
|
Load the skill; never act from memory of it.
|
|
13100
13550
|
`.trim();
|
|
13101
13551
|
var init_methods = __esm(() => {
|
|
@@ -13118,7 +13568,7 @@ function reconcileResume(view, observations = []) {
|
|
|
13118
13568
|
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
13119
13569
|
if (!candidate.ok)
|
|
13120
13570
|
return candidate;
|
|
13121
|
-
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13571
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data, treeFreshness(view.workspace.root)).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
13122
13572
|
const blockers = [...view.task.progress.blockers];
|
|
13123
13573
|
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
13124
13574
|
blockers.push({
|
|
@@ -13608,10 +14058,7 @@ class WorkitCore {
|
|
|
13608
14058
|
if (finding.data.disposition !== "fixed")
|
|
13609
14059
|
return finding;
|
|
13610
14060
|
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
|
-
]));
|
|
14061
|
+
const statuses = new Map(evaluateEvidence(withEntry, currentCandidate.data, treeFreshness(this.store.root)).map((item) => [item.evidenceId, item.status]));
|
|
13615
14062
|
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
13616
14063
|
kind: item.data.kind,
|
|
13617
14064
|
candidateId: item.data.candidateId,
|
|
@@ -13626,6 +14073,71 @@ class WorkitCore {
|
|
|
13626
14073
|
return changed;
|
|
13627
14074
|
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
13628
14075
|
}
|
|
14076
|
+
observeCheck(request) {
|
|
14077
|
+
return this.retryOmittedRevisions(request, () => this.observeCheckOnce(request));
|
|
14078
|
+
}
|
|
14079
|
+
observeCheckOnce(request) {
|
|
14080
|
+
const root = this.contextRootError();
|
|
14081
|
+
if (!root.ok)
|
|
14082
|
+
return root;
|
|
14083
|
+
if (this.context.caller.host !== "workit_cli" || (this.context.workerId ?? null) !== null)
|
|
14084
|
+
return failure2("permission_denied", "only the workit CLI records observed checks");
|
|
14085
|
+
const observation = checkObservationSchema.safeParse(request?.observation);
|
|
14086
|
+
if (!observation.success)
|
|
14087
|
+
return failure2("invalid_input", "check observation is invalid", {
|
|
14088
|
+
fields: observation.error.issues.map((issue) => ({
|
|
14089
|
+
path: issue.path.join("."),
|
|
14090
|
+
reason: issue.message
|
|
14091
|
+
}))
|
|
14092
|
+
});
|
|
14093
|
+
const task = this.store.readTask(request.taskId);
|
|
14094
|
+
if (!task.ok)
|
|
14095
|
+
return task;
|
|
14096
|
+
if (task.data.status === "closed")
|
|
14097
|
+
return failure2("invalid_transition", "closed task cannot record evidence");
|
|
14098
|
+
const observed = observation.data;
|
|
14099
|
+
let candidate = null;
|
|
14100
|
+
if (observed.tree === null) {
|
|
14101
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
14102
|
+
if (!captured.ok)
|
|
14103
|
+
return captured;
|
|
14104
|
+
candidate = captured.data;
|
|
14105
|
+
}
|
|
14106
|
+
const label = observed.name ?? observed.argv.join(" ");
|
|
14107
|
+
const data = {
|
|
14108
|
+
kind: "check",
|
|
14109
|
+
claim: `workit check ${label}`.slice(0, 500),
|
|
14110
|
+
requirementIds: [],
|
|
14111
|
+
...candidate ? { beforeCandidateId: candidate.id, candidateId: candidate.id } : {},
|
|
14112
|
+
result: observed.exitCode === 0 ? "passed" : "failed",
|
|
14113
|
+
summary: `${observed.configured ? "configured" : "ad-hoc"} check exited ${observed.exitCode}${observed.timedOut ? " (timed out)" : ""} in ${observed.durationMs} ms`,
|
|
14114
|
+
refs: [],
|
|
14115
|
+
exitCode: observed.exitCode,
|
|
14116
|
+
reviewContext: null,
|
|
14117
|
+
observation: observed
|
|
14118
|
+
};
|
|
14119
|
+
const changed = this.store.mutateTask(task.data.id, request.expectedRevision ?? task.data.revision, (current, mutation) => {
|
|
14120
|
+
const candidates = candidate === null || current.candidates.some((item) => item.id === candidate.id) ? current.candidates : [...current.candidates, candidate];
|
|
14121
|
+
const critical = [...new Set([...current.critical ?? [], CHECK_OBSERVATION_PATH])];
|
|
14122
|
+
return success(mutation.revision, null, {
|
|
14123
|
+
...current,
|
|
14124
|
+
critical,
|
|
14125
|
+
candidates,
|
|
14126
|
+
evidence: [
|
|
14127
|
+
...current.evidence,
|
|
14128
|
+
{
|
|
14129
|
+
id: newId(),
|
|
14130
|
+
recordedAt: mutation.now,
|
|
14131
|
+
provenance: provenance(this.context, "host_observed"),
|
|
14132
|
+
data
|
|
14133
|
+
}
|
|
14134
|
+
]
|
|
14135
|
+
});
|
|
14136
|
+
}, trustedNow(this.context));
|
|
14137
|
+
if (!changed.ok)
|
|
14138
|
+
return changed;
|
|
14139
|
+
return success(changed.data.revision, null, changed.data.evidence.at(-1));
|
|
14140
|
+
}
|
|
13629
14141
|
decision(request) {
|
|
13630
14142
|
return this.recordDecision(request);
|
|
13631
14143
|
}
|
|
@@ -13861,7 +14373,7 @@ class WorkitCore {
|
|
|
13861
14373
|
const current = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
13862
14374
|
if (!current.ok)
|
|
13863
14375
|
return current;
|
|
13864
|
-
const evaluations = evaluateEvidence(task.data, current.data);
|
|
14376
|
+
const evaluations = evaluateEvidence(task.data, current.data, treeFreshness(this.store.root));
|
|
13865
14377
|
const verified = evidence.some((entry) => {
|
|
13866
14378
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
13867
14379
|
return entry.recordedAt >= finding.recordedAt && findingVerificationPasses(finding.data.candidateId, {
|
|
@@ -14565,7 +15077,8 @@ class WorkitCore {
|
|
|
14565
15077
|
if (!current.ok)
|
|
14566
15078
|
return current;
|
|
14567
15079
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
14568
|
-
const
|
|
15080
|
+
const tree = treeFreshness(this.store.root);
|
|
15081
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
14569
15082
|
return success(task.revision, workspace.data.revision, {
|
|
14570
15083
|
id: task.id,
|
|
14571
15084
|
revision: task.revision,
|
|
@@ -14582,7 +15095,7 @@ class WorkitCore {
|
|
|
14582
15095
|
writer: evaluationWorkspace.writer
|
|
14583
15096
|
});
|
|
14584
15097
|
}
|
|
14585
|
-
compactContext(taskId) {
|
|
15098
|
+
compactContext(taskId, freshness) {
|
|
14586
15099
|
const root = this.contextRootError();
|
|
14587
15100
|
if (!root.ok)
|
|
14588
15101
|
return root;
|
|
@@ -14592,12 +15105,12 @@ class WorkitCore {
|
|
|
14592
15105
|
const helper = this.helperTaskGuard(task.data, true);
|
|
14593
15106
|
if (!helper.ok)
|
|
14594
15107
|
return helper;
|
|
14595
|
-
const view = this.view(task.data, false);
|
|
15108
|
+
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
14596
15109
|
if (!view.ok)
|
|
14597
15110
|
return view;
|
|
14598
15111
|
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
14599
15112
|
}
|
|
14600
|
-
view(task, capture = true) {
|
|
15113
|
+
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
14601
15114
|
const workspace = this.store.readWorkspace();
|
|
14602
15115
|
if (!workspace.ok)
|
|
14603
15116
|
return workspace;
|
|
@@ -14608,13 +15121,14 @@ class WorkitCore {
|
|
|
14608
15121
|
if (!current.ok)
|
|
14609
15122
|
return current;
|
|
14610
15123
|
const evaluationWorkspace = task.status === "closed" ? { ...workspace.data, writer: null } : workspace.data;
|
|
14611
|
-
const
|
|
15124
|
+
const tree = freshness;
|
|
15125
|
+
const requirements = evaluateRequirements(task, evaluationWorkspace, this.context.capabilities, current.data, this.store.root, this.context.caller, tree);
|
|
14612
15126
|
return success(task.revision, workspace.data.revision, {
|
|
14613
15127
|
task,
|
|
14614
15128
|
workspace: evaluationWorkspace,
|
|
14615
15129
|
capabilities: this.context.capabilities,
|
|
14616
15130
|
requirements,
|
|
14617
|
-
evidence: evaluateEvidence(task, current.data)
|
|
15131
|
+
evidence: evaluateEvidence(task, current.data, tree)
|
|
14618
15132
|
});
|
|
14619
15133
|
}
|
|
14620
15134
|
reconcileResume(view, observations = []) {
|
|
@@ -14841,13 +15355,14 @@ class WorkitCore {
|
|
|
14841
15355
|
return current;
|
|
14842
15356
|
const withCandidate = current.data;
|
|
14843
15357
|
const taskForView = withCandidate.id === task.data.candidates.at(-1)?.id ? task.data : { ...task.data, candidates: [...task.data.candidates, withCandidate] };
|
|
14844
|
-
const
|
|
15358
|
+
const tree = treeFreshness(this.store.root);
|
|
15359
|
+
const requirements = evaluateRequirements(taskForView, workspace.data, this.context.capabilities, withCandidate, this.store.root, this.context.caller, tree);
|
|
14845
15360
|
const view = {
|
|
14846
15361
|
task: taskForView,
|
|
14847
15362
|
workspace: workspace.data,
|
|
14848
15363
|
capabilities: this.context.capabilities,
|
|
14849
15364
|
requirements,
|
|
14850
|
-
evidence: evaluateEvidence(taskForView, withCandidate)
|
|
15365
|
+
evidence: evaluateEvidence(taskForView, withCandidate, tree)
|
|
14851
15366
|
};
|
|
14852
15367
|
const closure = evaluateClosure(input.outcome, view);
|
|
14853
15368
|
if (!closure.ok)
|
|
@@ -15297,7 +15812,7 @@ var support = (descriptor, axis) => {
|
|
|
15297
15812
|
});
|
|
15298
15813
|
|
|
15299
15814
|
// packages/workit-core/src/hooks/context.ts
|
|
15300
|
-
import
|
|
15815
|
+
import path13 from "node:path";
|
|
15301
15816
|
function sessionCompactContext(store, session, context, selection = "session-bound") {
|
|
15302
15817
|
const listed = store.listTaskIndex();
|
|
15303
15818
|
if (!listed.ok)
|
|
@@ -15320,18 +15835,26 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
15320
15835
|
capabilities: context.capabilities
|
|
15321
15836
|
});
|
|
15322
15837
|
const hit = cache.get(slot);
|
|
15323
|
-
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files))
|
|
15838
|
+
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
15324
15839
|
return hit.value;
|
|
15325
15840
|
const task = store.readTask(entry.id);
|
|
15326
15841
|
if (!task.ok)
|
|
15327
15842
|
return null;
|
|
15328
|
-
const
|
|
15329
|
-
const observed =
|
|
15330
|
-
const
|
|
15843
|
+
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
15844
|
+
const observed = task.data.evidence.some(isObservedCheck);
|
|
15845
|
+
const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path13.resolve(store.root, ref.path)] : []));
|
|
15846
|
+
const observedFiles = filesKey(files);
|
|
15847
|
+
const compact = new WorkitCore(store, context).compactContext(entry.id, freshness);
|
|
15331
15848
|
if (!compact.ok)
|
|
15332
15849
|
return null;
|
|
15333
15850
|
cache.delete(slot);
|
|
15334
|
-
cache.set(slot, {
|
|
15851
|
+
cache.set(slot, {
|
|
15852
|
+
key,
|
|
15853
|
+
files,
|
|
15854
|
+
filesKey: observedFiles,
|
|
15855
|
+
signal: observed ? freshness.current() : null,
|
|
15856
|
+
value: compact.data
|
|
15857
|
+
});
|
|
15335
15858
|
if (cache.size > CACHE_LIMIT)
|
|
15336
15859
|
cache.delete(cache.keys().next().value);
|
|
15337
15860
|
return compact.data;
|
|
@@ -15352,7 +15875,17 @@ ${tasks.map((task) => `- ${task.id} [${task.status}; source ${task.source.host}/
|
|
|
15352
15875
|
}, filesKey = (files) => {
|
|
15353
15876
|
const signatures = files.map((file) => fileSignature(file) ?? "-");
|
|
15354
15877
|
return signatures.some((item) => item !== "-" && racySignature(item)) ? null : signatures.join("\x00");
|
|
15355
|
-
}, CACHE_LIMIT = 64,
|
|
15878
|
+
}, CACHE_LIMIT = 64, SIGNAL_TTL_MS = 1500, signals, recentSignal = (root) => {
|
|
15879
|
+
const now = Date.now();
|
|
15880
|
+
const cached = signals.get(root);
|
|
15881
|
+
if (cached && now - cached.at < SIGNAL_TTL_MS)
|
|
15882
|
+
return cached.value;
|
|
15883
|
+
const value = worktreeSignal(root);
|
|
15884
|
+
signals.set(root, { at: now, value });
|
|
15885
|
+
if (signals.size > CACHE_LIMIT)
|
|
15886
|
+
signals.delete(signals.keys().next().value);
|
|
15887
|
+
return value;
|
|
15888
|
+
}, cache, utcNow = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), hookOperationContext = (input, descriptor) => ({
|
|
15356
15889
|
root: input.cwd,
|
|
15357
15890
|
caller: { host: input.host, actor: input.session.id },
|
|
15358
15891
|
callerAttested: false,
|
|
@@ -15395,8 +15928,11 @@ ${options.addendum}` : ""}
|
|
|
15395
15928
|
var init_context = __esm(() => {
|
|
15396
15929
|
init_methods();
|
|
15397
15930
|
init_task_contract();
|
|
15931
|
+
init_task_evaluation();
|
|
15932
|
+
init_rev();
|
|
15398
15933
|
init_task_engine();
|
|
15399
15934
|
init_task_store();
|
|
15935
|
+
signals = new Map;
|
|
15400
15936
|
cache = new Map;
|
|
15401
15937
|
});
|
|
15402
15938
|
|
|
@@ -15504,9 +16040,9 @@ var init_handle = __esm(() => {
|
|
|
15504
16040
|
|
|
15505
16041
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
15506
16042
|
import { existsSync as existsSync5, realpathSync as realpathSync7, statSync as statSync2 } from "node:fs";
|
|
15507
|
-
import
|
|
15508
|
-
var
|
|
15509
|
-
if (!nonEmpty2(value) || !
|
|
16043
|
+
import path14 from "node:path";
|
|
16044
|
+
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) => {
|
|
16045
|
+
if (!nonEmpty2(value) || !path14.isAbsolute(value) || !existsSync5(value))
|
|
15510
16046
|
return null;
|
|
15511
16047
|
try {
|
|
15512
16048
|
return statSync2(value).isDirectory() ? realpathSync7(value) : null;
|
|
@@ -15538,7 +16074,7 @@ var init_fields = __esm(() => {
|
|
|
15538
16074
|
});
|
|
15539
16075
|
|
|
15540
16076
|
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
15541
|
-
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) =>
|
|
16077
|
+
var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value) => isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null, eventOf = (name, value) => {
|
|
15542
16078
|
const toolUseId = optionalText(value.tool_use_id);
|
|
15543
16079
|
switch (name) {
|
|
15544
16080
|
case "SessionStart":
|
|
@@ -15557,7 +16093,7 @@ var CLAUDE_CODE_DESCRIPTOR, EVENTS, SOURCES, SHELL_TOOLS, toolCommand = (value)
|
|
|
15557
16093
|
const command = toolCommand(value);
|
|
15558
16094
|
if (!SHELL_TOOLS.has(String(value.tool_name)) || !command)
|
|
15559
16095
|
return { ok: false, error: "only shell PostToolUse is mapped" };
|
|
15560
|
-
const response =
|
|
16096
|
+
const response = isRecord3(value.tool_response) ? value.tool_response : {};
|
|
15561
16097
|
return {
|
|
15562
16098
|
ok: true,
|
|
15563
16099
|
event: {
|
|
@@ -15739,14 +16275,14 @@ var init_claude_code = __esm(() => {
|
|
|
15739
16275
|
claudeCodeAdapter = {
|
|
15740
16276
|
descriptor: CLAUDE_CODE_DESCRIPTOR,
|
|
15741
16277
|
parse(raw) {
|
|
15742
|
-
const native =
|
|
16278
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
15743
16279
|
const fail = (error) => ({
|
|
15744
16280
|
ok: false,
|
|
15745
16281
|
error,
|
|
15746
16282
|
native,
|
|
15747
16283
|
event: native ? EVENTS[native] : null
|
|
15748
16284
|
});
|
|
15749
|
-
if (!
|
|
16285
|
+
if (!isRecord3(raw) || !native)
|
|
15750
16286
|
return fail("hook_event_name is required");
|
|
15751
16287
|
if (!nonEmpty2(raw.session_id))
|
|
15752
16288
|
return fail("session_id is required");
|
|
@@ -15783,7 +16319,7 @@ function detectCodexSurface(env) {
|
|
|
15783
16319
|
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
|
|
15784
16320
|
}
|
|
15785
16321
|
var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name) => SHELL_TOOLS2.has(String(name).toLowerCase()), SOURCES2, parseCodexHookInput = (value) => {
|
|
15786
|
-
if (!
|
|
16322
|
+
if (!isRecord3(value) || !Object.hasOwn(EVENTS2, String(value.hook_event_name)))
|
|
15787
16323
|
return { ok: false, error: "hook_event_name is required" };
|
|
15788
16324
|
const event = value.hook_event_name;
|
|
15789
16325
|
const cwd = existingDirectory(value.cwd);
|
|
@@ -15791,7 +16327,7 @@ var undocumented, CODEX_DESCRIPTOR, EVENTS2, SHELL_TOOLS2, isShellTool = (name)
|
|
|
15791
16327
|
return { ok: false, error: "cwd must be an existing absolute directory" };
|
|
15792
16328
|
if (event === "PreToolUse" && !nonEmpty2(value.tool_name))
|
|
15793
16329
|
return { ok: false, error: "tool_name is required" };
|
|
15794
|
-
const command =
|
|
16330
|
+
const command = isRecord3(value.tool_input) ? commandText(value.tool_input.command) : null;
|
|
15795
16331
|
if (event === "PreToolUse" && isShellTool(value.tool_name) && !command)
|
|
15796
16332
|
return { ok: false, error: "tool_input.command is required for shell tools" };
|
|
15797
16333
|
if (event === "SubagentStart" && !nonEmpty2(value.agent_id))
|
|
@@ -15985,7 +16521,7 @@ var init_codex = __esm(() => {
|
|
|
15985
16521
|
const host = detectCodexSurface(env);
|
|
15986
16522
|
const parsed = parseCodexHookInput(raw);
|
|
15987
16523
|
if (!parsed.ok) {
|
|
15988
|
-
const native =
|
|
16524
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS2, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
15989
16525
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS2[native] : null };
|
|
15990
16526
|
}
|
|
15991
16527
|
const input = parsed.data;
|
|
@@ -16013,9 +16549,9 @@ var init_codex = __esm(() => {
|
|
|
16013
16549
|
});
|
|
16014
16550
|
|
|
16015
16551
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
16016
|
-
import
|
|
16552
|
+
import path15 from "node:path";
|
|
16017
16553
|
var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (value) => {
|
|
16018
|
-
if (!
|
|
16554
|
+
if (!isRecord3(value) || !Object.hasOwn(EVENTS3, String(value.hook_event_name)))
|
|
16019
16555
|
return { ok: false, error: "hook_event_name is required" };
|
|
16020
16556
|
const roots = value.workspace_roots;
|
|
16021
16557
|
if (!Array.isArray(roots) || roots.length === 0 || !roots.every(nonEmpty2))
|
|
@@ -16077,7 +16613,7 @@ var undocumented2, none, CURSOR_DESCRIPTOR, EVENTS3, parseCursorHookInput = (val
|
|
|
16077
16613
|
case "preCompact":
|
|
16078
16614
|
return { kind: "compact.pre", trigger: "auto" };
|
|
16079
16615
|
}
|
|
16080
|
-
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
16616
|
+
}, within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path15.sep) ? root : `${root}${path15.sep}`), hookCwd = (input) => {
|
|
16081
16617
|
const cwd = existingDirectory(input.cwd);
|
|
16082
16618
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
|
16083
16619
|
return cwd;
|
|
@@ -16229,7 +16765,7 @@ var init_cursor = __esm(() => {
|
|
|
16229
16765
|
parse(raw) {
|
|
16230
16766
|
const parsed = parseCursorHookInput(raw);
|
|
16231
16767
|
if (!parsed.ok) {
|
|
16232
|
-
const native =
|
|
16768
|
+
const native = isRecord3(raw) && Object.hasOwn(EVENTS3, String(raw.hook_event_name)) ? raw.hook_event_name : null;
|
|
16233
16769
|
return { ok: false, error: parsed.error, native, event: native ? EVENTS3[native] : null };
|
|
16234
16770
|
}
|
|
16235
16771
|
const input = parsed.data;
|