@brainervirus/workit-cursor 1.0.11 → 1.1.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 +732 -244
- package/dist/mcp-server.js +4130 -3842
- package/dist/workit-hook.js +729 -243
- package/package.json +3 -3
package/dist/workit-hook.js
CHANGED
|
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
|
|
|
3
3
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
4
4
|
|
|
5
5
|
// packages/workit-cursor/hooks/workit-hook.ts
|
|
6
|
-
import { existsSync as
|
|
7
|
-
import
|
|
6
|
+
import { existsSync as existsSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
7
|
+
import path9 from "node:path";
|
|
8
8
|
|
|
9
9
|
// packages/workit-core/src/core/task-contract.ts
|
|
10
10
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -7353,8 +7353,97 @@ var refSchema = discriminatedUnion("kind", [
|
|
|
7353
7353
|
id
|
|
7354
7354
|
}).strict(),
|
|
7355
7355
|
object({ kind: literal("host"), host: hostSchema, handle: nonEmpty }).strict(),
|
|
7356
|
-
object({ kind: literal("external"), url: text.url() }).strict()
|
|
7356
|
+
object({ kind: literal("external"), url: text.url() }).strict(),
|
|
7357
|
+
object({
|
|
7358
|
+
kind: literal("standing"),
|
|
7359
|
+
workspace: nonEmpty,
|
|
7360
|
+
class: nonEmpty,
|
|
7361
|
+
configDigest: nullableDigest
|
|
7362
|
+
}).strict()
|
|
7357
7363
|
]);
|
|
7364
|
+
var rewriteUnknown = (value, map) => {
|
|
7365
|
+
if (Array.isArray(value)) {
|
|
7366
|
+
const out = [];
|
|
7367
|
+
for (const item of value) {
|
|
7368
|
+
const rewritten = rewriteUnknown(item, map);
|
|
7369
|
+
if (!rewritten.drop)
|
|
7370
|
+
out.push(rewritten.value);
|
|
7371
|
+
}
|
|
7372
|
+
return { drop: false, value: out };
|
|
7373
|
+
}
|
|
7374
|
+
if (typeof value === "object" && value !== null) {
|
|
7375
|
+
if (refSchema.safeParse(value).success) {
|
|
7376
|
+
const mapped = map(value);
|
|
7377
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7378
|
+
}
|
|
7379
|
+
const out = {};
|
|
7380
|
+
for (const [key, item] of Object.entries(value)) {
|
|
7381
|
+
const rewritten = rewriteUnknown(item, map);
|
|
7382
|
+
out[key] = rewritten.drop ? null : rewritten.value;
|
|
7383
|
+
}
|
|
7384
|
+
return { drop: false, value: out };
|
|
7385
|
+
}
|
|
7386
|
+
return { drop: false, value };
|
|
7387
|
+
};
|
|
7388
|
+
var rewriteNode = (value, schema, map) => {
|
|
7389
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value) && refSchema.safeParse(value).success) {
|
|
7390
|
+
const mapped = map(value);
|
|
7391
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7392
|
+
}
|
|
7393
|
+
let node = schema;
|
|
7394
|
+
while (node instanceof ZodNullable || node instanceof ZodOptional || node instanceof ZodDefault) {
|
|
7395
|
+
node = node.unwrap();
|
|
7396
|
+
}
|
|
7397
|
+
if (node === refSchema) {
|
|
7398
|
+
if (!refSchema.safeParse(value).success)
|
|
7399
|
+
return { drop: false, value };
|
|
7400
|
+
const mapped = map(value);
|
|
7401
|
+
return mapped === null ? { drop: true } : { drop: false, value: mapped };
|
|
7402
|
+
}
|
|
7403
|
+
if (node instanceof ZodArray) {
|
|
7404
|
+
if (!Array.isArray(value))
|
|
7405
|
+
return { drop: false, value };
|
|
7406
|
+
const out = [];
|
|
7407
|
+
for (const item of value) {
|
|
7408
|
+
const rewritten = rewriteNode(item, node.element, map);
|
|
7409
|
+
if (!rewritten.drop)
|
|
7410
|
+
out.push(rewritten.value);
|
|
7411
|
+
}
|
|
7412
|
+
return { drop: false, value: out };
|
|
7413
|
+
}
|
|
7414
|
+
if (node instanceof ZodObject) {
|
|
7415
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
7416
|
+
return { drop: false, value };
|
|
7417
|
+
const shape = node.shape;
|
|
7418
|
+
const record = value;
|
|
7419
|
+
const out = {};
|
|
7420
|
+
for (const [key, item] of Object.entries(record)) {
|
|
7421
|
+
if (!(key in shape)) {
|
|
7422
|
+
const scrubbed = rewriteUnknown(item, map);
|
|
7423
|
+
out[key] = scrubbed.drop ? null : scrubbed.value;
|
|
7424
|
+
continue;
|
|
7425
|
+
}
|
|
7426
|
+
const child = shape[key];
|
|
7427
|
+
const rewritten = rewriteNode(item, child, map);
|
|
7428
|
+
if (rewritten.drop) {
|
|
7429
|
+
if (child instanceof ZodOptional || child instanceof ZodDefault)
|
|
7430
|
+
continue;
|
|
7431
|
+
if (child instanceof ZodNullable) {
|
|
7432
|
+
out[key] = null;
|
|
7433
|
+
continue;
|
|
7434
|
+
}
|
|
7435
|
+
return { drop: true };
|
|
7436
|
+
}
|
|
7437
|
+
out[key] = rewritten.value;
|
|
7438
|
+
}
|
|
7439
|
+
return { drop: false, value: out };
|
|
7440
|
+
}
|
|
7441
|
+
return { drop: false, value };
|
|
7442
|
+
};
|
|
7443
|
+
var rewriteRecordRefs = (value, schema, map) => {
|
|
7444
|
+
const rewritten = rewriteNode(value, schema, map);
|
|
7445
|
+
return rewritten.drop ? null : rewritten.value;
|
|
7446
|
+
};
|
|
7358
7447
|
var provenanceSchema = object({
|
|
7359
7448
|
kind: _enum(["host_observed", "agent_reported", "imported"]),
|
|
7360
7449
|
host: hostSchema,
|
|
@@ -7575,7 +7664,8 @@ var decisionSchema = object({
|
|
|
7575
7664
|
approvedContent: text,
|
|
7576
7665
|
displayed: text.optional(),
|
|
7577
7666
|
contentRefs: array(refSchema),
|
|
7578
|
-
statedChoice: object({ ref: text, text }).strict().optional()
|
|
7667
|
+
statedChoice: object({ ref: text, text }).strict().optional(),
|
|
7668
|
+
standing: object({ workspace: nonEmpty, class: nonEmpty }).strict().optional()
|
|
7579
7669
|
}).strict(),
|
|
7580
7670
|
digest,
|
|
7581
7671
|
response: _enum(["approved", "rejected", "stated"]),
|
|
@@ -9005,11 +9095,11 @@ class TaskStore {
|
|
|
9005
9095
|
return workspace;
|
|
9006
9096
|
if (!workspace.data)
|
|
9007
9097
|
return failure("not_found", "workspace not found");
|
|
9008
|
-
const
|
|
9009
|
-
if (!
|
|
9098
|
+
const expected = input.expectedRevision;
|
|
9099
|
+
if (!expected)
|
|
9010
9100
|
return failure("invalid_input", "task revision is required");
|
|
9011
|
-
if (task.data.revision !==
|
|
9012
|
-
return this.conflict(
|
|
9101
|
+
if (task.data.revision !== expected)
|
|
9102
|
+
return this.conflict(expected, task.data.revision);
|
|
9013
9103
|
if (workspace.data.revision !== input.expectedWorkspaceRevision)
|
|
9014
9104
|
return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
|
|
9015
9105
|
const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
|
|
@@ -9543,6 +9633,7 @@ var same = (left, right) => {
|
|
|
9543
9633
|
};
|
|
9544
9634
|
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9545
9635
|
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
9636
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
9546
9637
|
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
9547
9638
|
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9548
9639
|
const code = char.charCodeAt(0);
|
|
@@ -9589,21 +9680,398 @@ function assertProductWriteAllowed(input) {
|
|
|
9589
9680
|
}
|
|
9590
9681
|
|
|
9591
9682
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
9592
|
-
import { createHash as
|
|
9683
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
9593
9684
|
import * as fs6 from "node:fs";
|
|
9594
9685
|
import { spawnSync } from "node:child_process";
|
|
9595
|
-
import
|
|
9686
|
+
import path8 from "node:path";
|
|
9596
9687
|
|
|
9597
9688
|
// packages/workit-core/src/core/authority.ts
|
|
9598
|
-
import { createHash as
|
|
9689
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9599
9690
|
import * as fs5 from "node:fs";
|
|
9691
|
+
import path7 from "node:path";
|
|
9692
|
+
|
|
9693
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
9694
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9695
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
9696
|
+
|
|
9697
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
9698
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
9699
|
+
import path6 from "node:path";
|
|
9700
|
+
|
|
9701
|
+
// packages/workit-core/src/core/config.ts
|
|
9702
|
+
import {
|
|
9703
|
+
copyFileSync,
|
|
9704
|
+
cpSync,
|
|
9705
|
+
existsSync as existsSync3,
|
|
9706
|
+
mkdirSync as mkdirSync2,
|
|
9707
|
+
readFileSync as readFileSync2,
|
|
9708
|
+
readdirSync as readdirSync2,
|
|
9709
|
+
writeFileSync as writeFileSync2
|
|
9710
|
+
} from "node:fs";
|
|
9711
|
+
import os from "node:os";
|
|
9600
9712
|
import path5 from "node:path";
|
|
9713
|
+
|
|
9714
|
+
// packages/workit-core/src/core/boundary.ts
|
|
9715
|
+
var EVENT = {
|
|
9716
|
+
initialization: "initialization",
|
|
9717
|
+
provenance: "provenance",
|
|
9718
|
+
assets: "assets",
|
|
9719
|
+
configurationSource: "configuration_source",
|
|
9720
|
+
hooks: "hooks",
|
|
9721
|
+
mcpConnection: "mcp_connection",
|
|
9722
|
+
migration: "migration",
|
|
9723
|
+
installSteps: "install_steps",
|
|
9724
|
+
uncaughtFailure: "uncaught_failure",
|
|
9725
|
+
toolsFailed: "tools_failed",
|
|
9726
|
+
doctor: "doctor"
|
|
9727
|
+
};
|
|
9728
|
+
var errorDetail = (err) => {
|
|
9729
|
+
if (err instanceof Error) {
|
|
9730
|
+
return { error_name: err.name, error: err.message };
|
|
9731
|
+
}
|
|
9732
|
+
return { error_name: "unknown", error: String(err) };
|
|
9733
|
+
};
|
|
9734
|
+
|
|
9735
|
+
// packages/workit-core/src/core/config.ts
|
|
9736
|
+
var diagnosticLogger;
|
|
9737
|
+
var PRESETS = {
|
|
9738
|
+
gitflow: {
|
|
9739
|
+
allowed: ["feature/*", "bugfix/*", "hotfix/*", "release/*"],
|
|
9740
|
+
protected: ["main", "develop", "master", "prod", "production"]
|
|
9741
|
+
},
|
|
9742
|
+
"github-flow": { allowed: ["*"], protected: ["main"] },
|
|
9743
|
+
"trunk-based": { allowed: ["*"], protected: ["main"] },
|
|
9744
|
+
custom: { allowed: [], protected: [] }
|
|
9745
|
+
};
|
|
9746
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workit");
|
|
9747
|
+
var migratedDir = null;
|
|
9748
|
+
var migrationFailed = false;
|
|
9749
|
+
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
9750
|
+
if (migratedDir === dir)
|
|
9751
|
+
return dir;
|
|
9752
|
+
if (process.env.WORKFLOW_TOOLKIT_CONFIG || process.env.WORKFLOW_TOOLKIT_CONFIG_DIR) {
|
|
9753
|
+
migratedDir = dir;
|
|
9754
|
+
return dir;
|
|
9755
|
+
}
|
|
9756
|
+
const legacy = path5.join(process.env.XDG_CONFIG_HOME || path5.join(os.homedir(), ".config"), "workflow-toolkit");
|
|
9757
|
+
if (!existsSync3(legacy)) {
|
|
9758
|
+
migratedDir = dir;
|
|
9759
|
+
return dir;
|
|
9760
|
+
}
|
|
9761
|
+
if (!migrationFailed && existsSync3(dir)) {
|
|
9762
|
+
migratedDir = dir;
|
|
9763
|
+
return dir;
|
|
9764
|
+
}
|
|
9765
|
+
migrationFailed = false;
|
|
9766
|
+
mkdirSync2(dir, { recursive: true });
|
|
9767
|
+
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
9768
|
+
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
9769
|
+
const src = path5.join(legacy, entry.name);
|
|
9770
|
+
const dest = path5.join(dir, entry.name);
|
|
9771
|
+
if (existsSync3(dest))
|
|
9772
|
+
continue;
|
|
9773
|
+
try {
|
|
9774
|
+
if (entry.isDirectory())
|
|
9775
|
+
cpSync(src, dest, { recursive: true });
|
|
9776
|
+
else if (entry.isFile())
|
|
9777
|
+
copyFileSync(src, dest);
|
|
9778
|
+
} catch (err) {
|
|
9779
|
+
migrationFailed = true;
|
|
9780
|
+
diagnosticLogger?.warn(EVENT.migration, { from: src, ok: false, ...errorDetail(err) });
|
|
9781
|
+
console.warn(`[workit] config migration: a file could not be copied to ${dir}; it will be retried on the next run`);
|
|
9782
|
+
}
|
|
9783
|
+
}
|
|
9784
|
+
if (!migrationFailed)
|
|
9785
|
+
migratedDir = dir;
|
|
9786
|
+
return dir;
|
|
9787
|
+
};
|
|
9788
|
+
var configDir = () => ensureConfigDir();
|
|
9789
|
+
var DEFAULTS = {
|
|
9790
|
+
locale: "en",
|
|
9791
|
+
localeOptions: ["en", "es-CL", "es-MX", "es-AR", "pt-BR"],
|
|
9792
|
+
timezone: "America/Santiago",
|
|
9793
|
+
branchPolicy: {
|
|
9794
|
+
preset: "gitflow",
|
|
9795
|
+
allowed: [...PRESETS.gitflow.allowed],
|
|
9796
|
+
protected: [...PRESETS.gitflow.protected]
|
|
9797
|
+
},
|
|
9798
|
+
commitPolicy: { preset: "conventional" }
|
|
9799
|
+
};
|
|
9800
|
+
var isConfigObject = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
9801
|
+
|
|
9802
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
9803
|
+
var readWorkspacesResult = (dir = configDir()) => {
|
|
9804
|
+
const file = path6.join(dir, "workspaces.json");
|
|
9805
|
+
let raw;
|
|
9806
|
+
try {
|
|
9807
|
+
raw = readFileSync3(file, "utf8");
|
|
9808
|
+
} catch {
|
|
9809
|
+
return { status: "missing", path: file, entries: [] };
|
|
9810
|
+
}
|
|
9811
|
+
let parsed;
|
|
9812
|
+
try {
|
|
9813
|
+
parsed = JSON.parse(raw);
|
|
9814
|
+
} catch {
|
|
9815
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not valid JSON` };
|
|
9816
|
+
}
|
|
9817
|
+
if (!isConfigObject(parsed)) {
|
|
9818
|
+
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
9819
|
+
}
|
|
9820
|
+
const list = parsed.workspaces;
|
|
9821
|
+
return {
|
|
9822
|
+
status: "valid",
|
|
9823
|
+
path: file,
|
|
9824
|
+
entries: Array.isArray(list) ? list : []
|
|
9825
|
+
};
|
|
9826
|
+
};
|
|
9827
|
+
var loadWorkspacesFrom = (dir) => readWorkspacesResult(dir).entries;
|
|
9828
|
+
var globToRegExp = (glob) => {
|
|
9829
|
+
let out = "";
|
|
9830
|
+
for (let i = 0;i < glob.length; i++) {
|
|
9831
|
+
const c = glob[i];
|
|
9832
|
+
if (c === "*") {
|
|
9833
|
+
if (glob[i + 1] === "*") {
|
|
9834
|
+
if (glob[i + 2] === "/") {
|
|
9835
|
+
if (out === "")
|
|
9836
|
+
out += "/?";
|
|
9837
|
+
out += "(?:[^/]+/)*";
|
|
9838
|
+
i += 2;
|
|
9839
|
+
} else {
|
|
9840
|
+
if (i + 2 >= glob.length) {
|
|
9841
|
+
if (out.endsWith("/")) {
|
|
9842
|
+
out = out.slice(0, -1);
|
|
9843
|
+
out += "(?:/.*)?";
|
|
9844
|
+
} else {
|
|
9845
|
+
out += ".*";
|
|
9846
|
+
}
|
|
9847
|
+
} else {
|
|
9848
|
+
out += ".*";
|
|
9849
|
+
}
|
|
9850
|
+
i++;
|
|
9851
|
+
}
|
|
9852
|
+
} else {
|
|
9853
|
+
out += "[^/]*";
|
|
9854
|
+
}
|
|
9855
|
+
} else {
|
|
9856
|
+
out += c.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
9857
|
+
}
|
|
9858
|
+
}
|
|
9859
|
+
return new RegExp(`^${out}$`);
|
|
9860
|
+
};
|
|
9861
|
+
var matchWorkspace = (glob, target) => globToRegExp(glob.replaceAll("\\", "/")).test(target.replaceAll("\\", "/"));
|
|
9862
|
+
var realpathOf = (p) => {
|
|
9863
|
+
const real = realpathSync2.native ?? realpathSync2;
|
|
9864
|
+
try {
|
|
9865
|
+
return real(p);
|
|
9866
|
+
} catch {
|
|
9867
|
+
return p;
|
|
9868
|
+
}
|
|
9869
|
+
};
|
|
9870
|
+
var canonicalGlob = (glob) => {
|
|
9871
|
+
const m = /[*?[\]{]/.exec(glob);
|
|
9872
|
+
const prefix = m ? glob.slice(0, m.index) : glob;
|
|
9873
|
+
const rest = m ? glob.slice(m.index) : "";
|
|
9874
|
+
if (!prefix)
|
|
9875
|
+
return glob;
|
|
9876
|
+
const real = realpathOf(prefix.replace(/\/+$/, "") || "/");
|
|
9877
|
+
return real + (prefix.endsWith("/") ? "/" : "") + rest;
|
|
9878
|
+
};
|
|
9879
|
+
var resolveWorkspaceFrom = (cwd, dir) => {
|
|
9880
|
+
const targets = [cwd, realpathOf(cwd)].map((p) => p.replaceAll("\\", "/"));
|
|
9881
|
+
for (const entry of loadWorkspacesFrom(dir)) {
|
|
9882
|
+
if (!entry || typeof entry !== "object")
|
|
9883
|
+
continue;
|
|
9884
|
+
const ws = entry;
|
|
9885
|
+
if (typeof ws.glob !== "string" || !ws.glob)
|
|
9886
|
+
continue;
|
|
9887
|
+
const glob = ws.glob.replaceAll("\\", "/");
|
|
9888
|
+
const canonical = canonicalGlob(glob);
|
|
9889
|
+
for (const target of targets) {
|
|
9890
|
+
if (matchWorkspace(glob, target))
|
|
9891
|
+
return ws;
|
|
9892
|
+
if (canonical !== glob && matchWorkspace(canonical, target))
|
|
9893
|
+
return ws;
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
return null;
|
|
9897
|
+
};
|
|
9898
|
+
|
|
9899
|
+
// packages/workit-core/src/core/branch.ts
|
|
9900
|
+
import { execFileSync } from "node:child_process";
|
|
9901
|
+
var verifyPushIdentity = (root, provider, account) => {
|
|
9902
|
+
if (!account || typeof account !== "string")
|
|
9903
|
+
return { ok: false, error: "push identity requires a configured area account" };
|
|
9904
|
+
const probe = provider === "gitlab" ? ["api", "user", "--jq", ".username"] : ["api", "user", "--jq", ".login"];
|
|
9905
|
+
const bin = provider === "gitlab" ? "glab" : "gh";
|
|
9906
|
+
let login;
|
|
9907
|
+
try {
|
|
9908
|
+
login = execFileSync(bin, [...probe], {
|
|
9909
|
+
cwd: root,
|
|
9910
|
+
encoding: "utf8",
|
|
9911
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9912
|
+
}).trim();
|
|
9913
|
+
} catch {
|
|
9914
|
+
return { ok: false, error: `${bin} identity could not be resolved` };
|
|
9915
|
+
}
|
|
9916
|
+
if (!login || login.toLowerCase() !== account.toLowerCase())
|
|
9917
|
+
return {
|
|
9918
|
+
ok: false,
|
|
9919
|
+
error: `push identity ${login || "(unknown)"} does not match area account ${account}`
|
|
9920
|
+
};
|
|
9921
|
+
return { ok: true };
|
|
9922
|
+
};
|
|
9923
|
+
|
|
9924
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
9925
|
+
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
9926
|
+
var OPERATION_CLASS = {
|
|
9927
|
+
"git.branch_setup": "branch",
|
|
9928
|
+
"git.commit": "commit",
|
|
9929
|
+
"git.push": "push",
|
|
9930
|
+
"hosting.pull_request": "pr",
|
|
9931
|
+
"hosting.merge": "merge"
|
|
9932
|
+
};
|
|
9933
|
+
var operationAutoClass = (operation) => typeof operation === "string" ? OPERATION_CLASS[operation] ?? null : null;
|
|
9934
|
+
var digestOf = (configDirPath) => {
|
|
9935
|
+
try {
|
|
9936
|
+
const raw = readFileSync4(`${configDirPath}/workspaces.json`, "utf8");
|
|
9937
|
+
return createHash3("sha256").update(raw).digest("hex");
|
|
9938
|
+
} catch {
|
|
9939
|
+
return null;
|
|
9940
|
+
}
|
|
9941
|
+
};
|
|
9942
|
+
var normalizeClasses = (value) => {
|
|
9943
|
+
if (value === true)
|
|
9944
|
+
return [...AUTO_CLASSES];
|
|
9945
|
+
if (!Array.isArray(value))
|
|
9946
|
+
return [];
|
|
9947
|
+
const seen = new Set;
|
|
9948
|
+
for (const item of value) {
|
|
9949
|
+
if (typeof item === "string" && AUTO_CLASSES.includes(item))
|
|
9950
|
+
seen.add(item);
|
|
9951
|
+
}
|
|
9952
|
+
return [...seen];
|
|
9953
|
+
};
|
|
9954
|
+
var resolveAutoApproval = (root, dir = configDir()) => {
|
|
9955
|
+
let match;
|
|
9956
|
+
try {
|
|
9957
|
+
match = resolveWorkspaceFrom(root, dir);
|
|
9958
|
+
} catch {
|
|
9959
|
+
return { status: "off" };
|
|
9960
|
+
}
|
|
9961
|
+
if (!match)
|
|
9962
|
+
return { status: "off" };
|
|
9963
|
+
const classes = normalizeClasses(match.autoApprove);
|
|
9964
|
+
if (classes.length === 0)
|
|
9965
|
+
return { status: "off" };
|
|
9966
|
+
return {
|
|
9967
|
+
status: "on",
|
|
9968
|
+
workspace: match.name,
|
|
9969
|
+
classes,
|
|
9970
|
+
account: typeof match.vcs?.account === "string" && match.vcs.account ? match.vcs.account : null,
|
|
9971
|
+
configDigest: digestOf(dir)
|
|
9972
|
+
};
|
|
9973
|
+
};
|
|
9974
|
+
var isStandingReceipt = (value) => typeof value === "object" && value !== null && value.kind === "standing";
|
|
9975
|
+
var standingApprovalLive = (root, receipt, cls, dir) => {
|
|
9976
|
+
if (!isStandingReceipt(receipt))
|
|
9977
|
+
return false;
|
|
9978
|
+
if (typeof cls !== "string" || !AUTO_CLASSES.includes(cls))
|
|
9979
|
+
return false;
|
|
9980
|
+
if (receipt.class !== cls)
|
|
9981
|
+
return false;
|
|
9982
|
+
const resolved = resolveAutoApproval(root, dir);
|
|
9983
|
+
if (resolved.status !== "on")
|
|
9984
|
+
return false;
|
|
9985
|
+
if (resolved.workspace !== receipt.workspace)
|
|
9986
|
+
return false;
|
|
9987
|
+
return resolved.classes.includes(cls);
|
|
9988
|
+
};
|
|
9989
|
+
var standingReceiptFor = (root, workspace, cls, dir) => {
|
|
9990
|
+
const resolved = resolveAutoApproval(root, dir);
|
|
9991
|
+
return {
|
|
9992
|
+
kind: "standing",
|
|
9993
|
+
workspace,
|
|
9994
|
+
class: cls,
|
|
9995
|
+
configDigest: resolved.status === "on" && resolved.workspace === workspace ? resolved.configDigest : null
|
|
9996
|
+
};
|
|
9997
|
+
};
|
|
9998
|
+
var verifyStandingApproval = (root, task, caller, binding) => {
|
|
9999
|
+
if (task.status !== "active")
|
|
10000
|
+
return failure("invalid_transition", "only active tasks can authorize actions");
|
|
10001
|
+
const session = task.intent?.provenance?.session;
|
|
10002
|
+
if (!session || session.kind !== "host" || session.host !== caller.host || session.handle !== caller.actor)
|
|
10003
|
+
return failure("permission_denied", "standing approval requires the lead session");
|
|
10004
|
+
const standing = binding.standing;
|
|
10005
|
+
if (!standing || typeof standing.workspace !== "string" || typeof standing.class !== "string")
|
|
10006
|
+
return failure("invalid_input", "standing approval needs a workspace and class");
|
|
10007
|
+
let operation;
|
|
10008
|
+
try {
|
|
10009
|
+
operation = typeof binding.approvedContent === "string" ? JSON.parse(binding.approvedContent).operation : null;
|
|
10010
|
+
} catch {
|
|
10011
|
+
operation = null;
|
|
10012
|
+
}
|
|
10013
|
+
const cls = operationAutoClass(operation);
|
|
10014
|
+
if (!cls || cls !== standing.class)
|
|
10015
|
+
return failure("permission_denied", "standing approval does not cover this operation");
|
|
10016
|
+
const resolved = resolveAutoApproval(root);
|
|
10017
|
+
if (resolved.status !== "on" || resolved.workspace !== standing.workspace || !resolved.classes.includes(cls))
|
|
10018
|
+
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
10019
|
+
if (cls === "push") {
|
|
10020
|
+
const workspace = resolveWorkspaceFrom(root, configDir());
|
|
10021
|
+
const provider = workspace?.vcs?.provider;
|
|
10022
|
+
const account = resolved.account;
|
|
10023
|
+
if (!provider || !account)
|
|
10024
|
+
return failure("permission_denied", "auto-push requires a workspace provider and area account");
|
|
10025
|
+
const identity = verifyPushIdentity(root, provider, account);
|
|
10026
|
+
if (!identity.ok)
|
|
10027
|
+
return failure("permission_denied", identity.error);
|
|
10028
|
+
}
|
|
10029
|
+
return success(null, null, {
|
|
10030
|
+
kind: "host_observed",
|
|
10031
|
+
host: caller.host,
|
|
10032
|
+
session: { kind: "host", host: caller.host, handle: caller.actor },
|
|
10033
|
+
workerId: null,
|
|
10034
|
+
receipts: [standingReceiptFor(root, standing.workspace, cls)]
|
|
10035
|
+
});
|
|
10036
|
+
};
|
|
10037
|
+
|
|
10038
|
+
// packages/workit-core/src/core/authority.ts
|
|
9601
10039
|
var verifiedAuthorities = new WeakMap;
|
|
9602
10040
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
9603
10041
|
const token = {};
|
|
9604
10042
|
verifiedAuthorities.set(token, { kind, provenance, ...expected, ...binding, caller });
|
|
9605
10043
|
return token;
|
|
9606
10044
|
};
|
|
10045
|
+
var chainStepKey = (step) => step.kind === "commit" ? step.message : step.kind === "branch" ? `branch:${step.target}` : "pr";
|
|
10046
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10047
|
+
var normalizeChainSteps = (steps) => {
|
|
10048
|
+
if (!Array.isArray(steps) || steps.length === 0 || steps.length > 32)
|
|
10049
|
+
return null;
|
|
10050
|
+
const normalized = [];
|
|
10051
|
+
for (const step of steps) {
|
|
10052
|
+
if (typeof step === "string") {
|
|
10053
|
+
if (!step.trim())
|
|
10054
|
+
return null;
|
|
10055
|
+
normalized.push({ kind: "commit", message: step });
|
|
10056
|
+
continue;
|
|
10057
|
+
}
|
|
10058
|
+
if (!isRecord(step))
|
|
10059
|
+
return null;
|
|
10060
|
+
if (typeof step.branch === "string" && step.branch.trim() && Object.keys(step).length === 1) {
|
|
10061
|
+
normalized.push({ kind: "branch", target: step.branch });
|
|
10062
|
+
continue;
|
|
10063
|
+
}
|
|
10064
|
+
if (step.pr === true && Object.keys(step).length === 1) {
|
|
10065
|
+
normalized.push({ kind: "pr" });
|
|
10066
|
+
continue;
|
|
10067
|
+
}
|
|
10068
|
+
return null;
|
|
10069
|
+
}
|
|
10070
|
+
const keys = normalized.map(chainStepKey);
|
|
10071
|
+
if (new Set(keys).size !== keys.length)
|
|
10072
|
+
return null;
|
|
10073
|
+
return normalized;
|
|
10074
|
+
};
|
|
9607
10075
|
var scopeEqual = (left, right) => canonicalJson(left) === canonicalJson(right);
|
|
9608
10076
|
var parseSteps = (content) => {
|
|
9609
10077
|
let value;
|
|
@@ -9615,7 +10083,10 @@ var parseSteps = (content) => {
|
|
|
9615
10083
|
if (typeof value !== "object" || value === null || !("steps" in value))
|
|
9616
10084
|
return [];
|
|
9617
10085
|
const steps = value.steps;
|
|
9618
|
-
|
|
10086
|
+
if (!Array.isArray(steps))
|
|
10087
|
+
return null;
|
|
10088
|
+
const normalized = normalizeChainSteps(steps);
|
|
10089
|
+
return normalized;
|
|
9619
10090
|
};
|
|
9620
10091
|
var decisionMatches = (entry, purpose, binding) => {
|
|
9621
10092
|
const decision = entry.data;
|
|
@@ -9711,7 +10182,7 @@ function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
|
9711
10182
|
return [];
|
|
9712
10183
|
return task.decisions.filter((entry) => entry.provenance.kind !== "imported" && decisionMatches(entry, purpose, binding) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, entry.data.binding).ok : entry.data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map((entry) => entry.data);
|
|
9713
10184
|
}
|
|
9714
|
-
var digestBytes2 = (bytes) =>
|
|
10185
|
+
var digestBytes2 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
|
|
9715
10186
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
9716
10187
|
let root;
|
|
9717
10188
|
try {
|
|
@@ -9724,23 +10195,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
9724
10195
|
continue;
|
|
9725
10196
|
if (!reference.digest)
|
|
9726
10197
|
return failure("invalid_input", "document references require a byte digest");
|
|
9727
|
-
const target =
|
|
9728
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
10198
|
+
const target = path7.resolve(root, reference.path);
|
|
10199
|
+
if (target !== root && !target.startsWith(`${root}${path7.sep}`))
|
|
9729
10200
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9730
10201
|
fields: [
|
|
9731
10202
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
9732
10203
|
]
|
|
9733
10204
|
});
|
|
9734
10205
|
try {
|
|
9735
|
-
const relative =
|
|
10206
|
+
const relative = path7.relative(root, target);
|
|
9736
10207
|
let current = root;
|
|
9737
|
-
for (const segment of relative.split(
|
|
10208
|
+
for (const segment of relative.split(path7.sep)) {
|
|
9738
10209
|
if (!segment)
|
|
9739
10210
|
continue;
|
|
9740
|
-
current =
|
|
10211
|
+
current = path7.join(current, segment);
|
|
9741
10212
|
const stat = fs5.lstatSync(current);
|
|
9742
10213
|
const real = fs5.realpathSync(current);
|
|
9743
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
10214
|
+
if (real !== root && !real.startsWith(`${root}${path7.sep}`))
|
|
9744
10215
|
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9745
10216
|
fields: [
|
|
9746
10217
|
{
|
|
@@ -9793,6 +10264,20 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
9793
10264
|
return failure("permission_denied", "decision was rejected");
|
|
9794
10265
|
if (entry.provenance.kind !== "host_observed" || entry.provenance.receipts.length === 0 || !provenanceMatches(entry.provenance, authority.provenance))
|
|
9795
10266
|
return failure("permission_denied", "action approval lacks native receipt assurance");
|
|
10267
|
+
const receipts = entry.provenance.receipts;
|
|
10268
|
+
const standing = receipts.filter((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "standing");
|
|
10269
|
+
const questionBacked = receipts.some((receipt) => typeof receipt === "object" && receipt !== null && receipt.kind === "host");
|
|
10270
|
+
if (!questionBacked && standing.length > 0) {
|
|
10271
|
+
let operation;
|
|
10272
|
+
try {
|
|
10273
|
+
operation = JSON.parse(decision.binding.approvedContent).operation;
|
|
10274
|
+
} catch {
|
|
10275
|
+
operation = null;
|
|
10276
|
+
}
|
|
10277
|
+
const cls = operationAutoClass(operation);
|
|
10278
|
+
if (!cls || !standing.some((receipt) => standingApprovalLive(store.root, receipt, cls)))
|
|
10279
|
+
return failure("permission_denied", "standing auto-approval is not live for this operation");
|
|
10280
|
+
}
|
|
9796
10281
|
if (decision.revoked)
|
|
9797
10282
|
return failure("permission_denied", "decision is revoked");
|
|
9798
10283
|
if (decision.digest !== decisionDigest(decision))
|
|
@@ -9806,14 +10291,12 @@ var validateAction = (store, task, workspaceId, input, authority) => {
|
|
|
9806
10291
|
return content;
|
|
9807
10292
|
const requestedSteps = parseSteps(decision.binding.approvedContent);
|
|
9808
10293
|
if (requestedSteps === null)
|
|
9809
|
-
return failure("invalid_input", "bounded action steps must be a
|
|
9810
|
-
if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps))
|
|
10294
|
+
return failure("invalid_input", "bounded action steps must be a unique non-empty array");
|
|
10295
|
+
if (input.steps && canonicalJson(input.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
|
|
9811
10296
|
return failure("permission_denied", "bounded action steps do not match approved content");
|
|
9812
|
-
if (requestedSteps.some((step) => !step || typeof step !== "string") || new Set(requestedSteps).size !== requestedSteps.length)
|
|
9813
|
-
return failure("invalid_input", "bounded action steps must be unique and non-empty");
|
|
9814
10297
|
const stored = task.actionProgress?.find((item) => item.decisionId === input.decisionId);
|
|
9815
|
-
const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps, completed: [] };
|
|
9816
|
-
if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps))
|
|
10298
|
+
const workflow = stored ? { steps: stored.steps, completed: stored.completedSteps } : { steps: requestedSteps.map(chainStepKey), completed: [] };
|
|
10299
|
+
if (workflow.steps.length && requestedSteps.length && canonicalJson(workflow.steps) !== canonicalJson(requestedSteps.map(chainStepKey)))
|
|
9817
10300
|
return failure("permission_denied", "bounded action scope changed");
|
|
9818
10301
|
return success(null, null, { entry, workflow });
|
|
9819
10302
|
};
|
|
@@ -10094,15 +10577,15 @@ var bindingCovers = scopeCovers;
|
|
|
10094
10577
|
var verifyDecisionContent = verifyContentRefs;
|
|
10095
10578
|
|
|
10096
10579
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
10097
|
-
var digestBytes3 = (value) =>
|
|
10098
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10099
|
-
var relative = (root, candidate) =>
|
|
10580
|
+
var digestBytes3 = (value) => createHash5("sha256").update(value).digest("hex");
|
|
10581
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path8.sep}`);
|
|
10582
|
+
var relative = (root, candidate) => path8.relative(root, candidate).split(path8.sep).join("/") || ".";
|
|
10100
10583
|
var canonicalPath = (value) => {
|
|
10101
10584
|
if (typeof value !== "string" || !value)
|
|
10102
10585
|
return null;
|
|
10103
10586
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
10104
10587
|
return null;
|
|
10105
|
-
const normalized =
|
|
10588
|
+
const normalized = path8.posix.normalize(value);
|
|
10106
10589
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
10107
10590
|
return null;
|
|
10108
10591
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10150,14 +10633,14 @@ var gitPaths = (root) => {
|
|
|
10150
10633
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10151
10634
|
};
|
|
10152
10635
|
}
|
|
10153
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10636
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")) : [];
|
|
10154
10637
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10155
10638
|
if (stagedDeleted.status !== 0) {
|
|
10156
10639
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10157
10640
|
if (!/not a git repository/i.test(stderr))
|
|
10158
10641
|
return { paths, uncertain: true, git: true };
|
|
10159
10642
|
} else if (stagedDeleted.stdout) {
|
|
10160
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10643
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path8.sep).join("/")));
|
|
10161
10644
|
}
|
|
10162
10645
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10163
10646
|
};
|
|
@@ -10172,7 +10655,7 @@ var walk = (root, directory, output) => {
|
|
|
10172
10655
|
for (const name of names) {
|
|
10173
10656
|
if (name === ".git" || name === ".workit")
|
|
10174
10657
|
continue;
|
|
10175
|
-
const target =
|
|
10658
|
+
const target = path8.join(directory, name);
|
|
10176
10659
|
let stat;
|
|
10177
10660
|
try {
|
|
10178
10661
|
stat = fs6.lstatSync(target);
|
|
@@ -10190,12 +10673,12 @@ var walk = (root, directory, output) => {
|
|
|
10190
10673
|
var scopeRoots = (root, scope) => {
|
|
10191
10674
|
const roots = [];
|
|
10192
10675
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10193
|
-
const target =
|
|
10676
|
+
const target = path8.resolve(root, item);
|
|
10194
10677
|
if (!inside(root, target))
|
|
10195
10678
|
return failure("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` }] });
|
|
10196
10679
|
let ancestor = target;
|
|
10197
10680
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10198
|
-
ancestor =
|
|
10681
|
+
ancestor = path8.dirname(ancestor);
|
|
10199
10682
|
try {
|
|
10200
10683
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10201
10684
|
return failure("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` }] });
|
|
@@ -10247,7 +10730,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10247
10730
|
for (const item of [...names].sort()) {
|
|
10248
10731
|
if (!scopeMatches(item, normalizedScope))
|
|
10249
10732
|
continue;
|
|
10250
|
-
const target =
|
|
10733
|
+
const target = path8.join(checkout, item);
|
|
10251
10734
|
try {
|
|
10252
10735
|
const stat = fs6.lstatSync(target);
|
|
10253
10736
|
if (stat.isSymbolicLink()) {
|
|
@@ -10368,13 +10851,28 @@ function evaluateEvidence(task, candidate) {
|
|
|
10368
10851
|
});
|
|
10369
10852
|
}
|
|
10370
10853
|
var scopeCovers2 = scopeCovers;
|
|
10854
|
+
var checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
10855
|
+
const pin = assignment.candidateId ?? null;
|
|
10856
|
+
if (pin === null)
|
|
10857
|
+
return true;
|
|
10858
|
+
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
10859
|
+
};
|
|
10860
|
+
var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
10861
|
+
const bound = evidence.kind === "check" || evidence.kind === "review";
|
|
10862
|
+
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
10863
|
+
return {
|
|
10864
|
+
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
10865
|
+
candidateId: evidence.candidateId ?? fallback
|
|
10866
|
+
};
|
|
10867
|
+
};
|
|
10868
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId === findingCandidateId);
|
|
10371
10869
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
10372
10870
|
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
10373
|
-
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10871
|
+
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
10374
10872
|
if (kind !== "passed")
|
|
10375
10873
|
return false;
|
|
10376
10874
|
if (dimension === "testing" || dimension === "verification")
|
|
10377
|
-
return evidenceKind === "check";
|
|
10875
|
+
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
10378
10876
|
if (dimension === "review")
|
|
10379
10877
|
return evidenceKind === "review";
|
|
10380
10878
|
if (dimension === "investigation" || dimension === "challenge")
|
|
@@ -10383,6 +10881,21 @@ var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
|
10383
10881
|
return evidenceKind === "artifact";
|
|
10384
10882
|
return false;
|
|
10385
10883
|
};
|
|
10884
|
+
var expectedKinds = (dimension, ruleId) => {
|
|
10885
|
+
if (dimension === "testing")
|
|
10886
|
+
return "check";
|
|
10887
|
+
if (dimension === "verification")
|
|
10888
|
+
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
10889
|
+
if (dimension === "review")
|
|
10890
|
+
return "review";
|
|
10891
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
10892
|
+
return "investigation";
|
|
10893
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
10894
|
+
return "artifact";
|
|
10895
|
+
if (dimension === "decisions")
|
|
10896
|
+
return "decision";
|
|
10897
|
+
return "worker report";
|
|
10898
|
+
};
|
|
10386
10899
|
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
10387
10900
|
const evidence = evaluateEvidence(task, candidate);
|
|
10388
10901
|
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
@@ -10396,7 +10909,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10396
10909
|
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
10397
10910
|
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
10398
10911
|
const passed = related.filter(({ entry, evaluation }) => {
|
|
10399
|
-
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension))
|
|
10912
|
+
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
10400
10913
|
return false;
|
|
10401
10914
|
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
10402
10915
|
return false;
|
|
@@ -10404,11 +10917,12 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10404
10917
|
return true;
|
|
10405
10918
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10406
10919
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10407
|
-
const sameEvidenceSession =
|
|
10920
|
+
const sameEvidenceSession = related.some(({ entry: other }) => other.id !== entry.id && other.data.kind === "review" && other.data.candidateId !== null && other.data.candidateId === entry.data.candidateId && sameSession(reviewSession, other.provenance.session));
|
|
10408
10921
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10409
10922
|
});
|
|
10410
10923
|
if (passed.length) {
|
|
10411
|
-
const
|
|
10924
|
+
const currentId = candidate?.id ?? null;
|
|
10925
|
+
const withRed = requirement.dimension === "testing" ? passed.filter(({ entry }) => related.some((other) => other.entry.data.kind === "check" && other.evaluation.status === "failed" && other.entry.data.requirementIds.includes(requirement.id) && other.entry.recordedAt <= entry.recordedAt) || related.some((baseline) => baseline.entry.id !== entry.id && baseline.entry.data.kind === "check" && baseline.entry.data.result === "passed" && baseline.entry.data.candidateId !== null && currentId !== null && baseline.entry.data.candidateId !== currentId)) : passed;
|
|
10412
10926
|
if (withRed.length)
|
|
10413
10927
|
return {
|
|
10414
10928
|
requirementId: requirement.id,
|
|
@@ -10457,6 +10971,14 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10457
10971
|
reason: "an applicable approved limitation permits the missing evidence"
|
|
10458
10972
|
};
|
|
10459
10973
|
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
10974
|
+
if (!unavailable && related.length > 0 && !passed.length)
|
|
10975
|
+
return {
|
|
10976
|
+
requirementId: requirement.id,
|
|
10977
|
+
status: "unsatisfied",
|
|
10978
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
10979
|
+
decisionIds: [],
|
|
10980
|
+
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
10981
|
+
};
|
|
10460
10982
|
return {
|
|
10461
10983
|
requirementId: requirement.id,
|
|
10462
10984
|
status: unavailable ? "unavailable" : "unsatisfied",
|
|
@@ -10481,7 +11003,13 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10481
11003
|
if (entry.data.disposition === "fixed") {
|
|
10482
11004
|
const verified = entry.data.resolution?.evidenceIds.some((id) => {
|
|
10483
11005
|
const evidence = view.task.evidence.find((item) => item.id === id);
|
|
10484
|
-
|
|
11006
|
+
if (!evidence)
|
|
11007
|
+
return false;
|
|
11008
|
+
return findingVerificationPasses(entry.data.candidateId, {
|
|
11009
|
+
kind: evidence.data.kind,
|
|
11010
|
+
candidateId: evidence.data.candidateId,
|
|
11011
|
+
status: evidenceById.get(id) ?? "missing"
|
|
11012
|
+
});
|
|
10485
11013
|
});
|
|
10486
11014
|
if (!verified)
|
|
10487
11015
|
return failure("requirements_unsatisfied", "fixed findings require current verification");
|
|
@@ -10497,7 +11025,12 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10497
11025
|
}
|
|
10498
11026
|
}
|
|
10499
11027
|
}
|
|
10500
|
-
const blocking = evaluations.filter((item) =>
|
|
11028
|
+
const blocking = evaluations.filter((item) => {
|
|
11029
|
+
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
11030
|
+
return false;
|
|
11031
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
11032
|
+
return !requirement || requirement.before === "close";
|
|
11033
|
+
});
|
|
10501
11034
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10502
11035
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10503
11036
|
requirementIds: blocking.map((item) => item.requirementId),
|
|
@@ -10540,15 +11073,15 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10540
11073
|
// packages/workit-core/src/core/methods.ts
|
|
10541
11074
|
var METHODS = {
|
|
10542
11075
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10543
|
-
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10544
|
-
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
11076
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
11077
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
10545
11078
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10546
11079
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10547
|
-
"workit-debug": {
|
|
10548
|
-
"workit-handoff": {
|
|
11080
|
+
"workit-debug": {},
|
|
11081
|
+
"workit-handoff": {},
|
|
10549
11082
|
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
10550
11083
|
};
|
|
10551
|
-
var methodMatches = (
|
|
11084
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
10552
11085
|
var assuranceFor = (id, definition, capabilities) => {
|
|
10553
11086
|
const keys = new Set([
|
|
10554
11087
|
id,
|
|
@@ -10569,7 +11102,7 @@ var assuranceFor = (id, definition, capabilities) => {
|
|
|
10569
11102
|
};
|
|
10570
11103
|
function selectMethods(policy, capabilities) {
|
|
10571
11104
|
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
10572
|
-
const requirements = policy.requirements.filter((requirement) => methodMatches(
|
|
11105
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
10573
11106
|
if (requirements.length === 0)
|
|
10574
11107
|
return [];
|
|
10575
11108
|
const capability = assuranceFor(id, definition, capabilities);
|
|
@@ -10770,6 +11303,13 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10770
11303
|
babysit: boolean2().optional()
|
|
10771
11304
|
}).strict()
|
|
10772
11305
|
}).strict(),
|
|
11306
|
+
object({
|
|
11307
|
+
operation: literal("hosting.merge"),
|
|
11308
|
+
payload: object({
|
|
11309
|
+
target_branch: string2().min(1).optional(),
|
|
11310
|
+
source_branch: string2().min(1).optional()
|
|
11311
|
+
}).strict()
|
|
11312
|
+
}).strict(),
|
|
10773
11313
|
object({
|
|
10774
11314
|
operation: literal("youtrack.update"),
|
|
10775
11315
|
payload: object({
|
|
@@ -10830,7 +11370,7 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10830
11370
|
}).strict()
|
|
10831
11371
|
]);
|
|
10832
11372
|
// packages/workit-core/src/core/task-engine.ts
|
|
10833
|
-
import { realpathSync as
|
|
11373
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
10834
11374
|
|
|
10835
11375
|
// packages/workit-core/src/core/policy-resolver.ts
|
|
10836
11376
|
var resolverInputSchema = object({
|
|
@@ -11274,6 +11814,8 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11274
11814
|
if (task.data.status === "paused" && (input.state === "running" || input.state === "cancelling"))
|
|
11275
11815
|
return failure("invalid_transition", "paused task cannot run a worker");
|
|
11276
11816
|
const notStarted = input.dispatch === true;
|
|
11817
|
+
if (notStarted && entry.data.state === "stopped" && input.session === null)
|
|
11818
|
+
return success(task.data.revision, workspace.data.revision, entry);
|
|
11277
11819
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
11278
11820
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
11279
11821
|
if (notStarted && entry.data.session !== null)
|
|
@@ -11305,7 +11847,7 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11305
11847
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
11306
11848
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
11307
11849
|
taskId: task.data.id,
|
|
11308
|
-
|
|
11850
|
+
expectedRevision: input.expectedRevision,
|
|
11309
11851
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
11310
11852
|
now: input.now,
|
|
11311
11853
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
@@ -11353,90 +11895,22 @@ var importedProvenance = (context) => ({
|
|
|
11353
11895
|
receipts: []
|
|
11354
11896
|
});
|
|
11355
11897
|
var portableTask = (task) => {
|
|
11356
|
-
const
|
|
11357
|
-
|
|
11358
|
-
|
|
11359
|
-
|
|
11360
|
-
|
|
11361
|
-
|
|
11362
|
-
|
|
11363
|
-
|
|
11364
|
-
|
|
11365
|
-
|
|
11366
|
-
|
|
11367
|
-
|
|
11368
|
-
|
|
11369
|
-
];
|
|
11370
|
-
for (const entry of entries)
|
|
11371
|
-
entry.provenance = portableProvenance(entry.provenance);
|
|
11372
|
-
for (const worker of clone.workers)
|
|
11373
|
-
worker.data.session = null;
|
|
11374
|
-
clone.intent = {
|
|
11375
|
-
...clone.intent,
|
|
11376
|
-
data: { ...clone.intent.data, authorityRefs: portableRefs(clone.intent.data.authorityRefs) }
|
|
11377
|
-
};
|
|
11378
|
-
clone.constraints = clone.constraints.flatMap((constraint) => {
|
|
11379
|
-
const source = portableRef(constraint.source);
|
|
11380
|
-
return source ? [
|
|
11381
|
-
{
|
|
11382
|
-
...constraint,
|
|
11383
|
-
source,
|
|
11384
|
-
requires: constraint.requires.map((requirement) => ({
|
|
11385
|
-
...requirement,
|
|
11386
|
-
refs: portableRefs(requirement.refs)
|
|
11387
|
-
}))
|
|
11388
|
-
}
|
|
11389
|
-
] : [];
|
|
11390
|
-
});
|
|
11391
|
-
clone.assessments = clone.assessments.map((entry) => ({
|
|
11392
|
-
...entry,
|
|
11393
|
-
data: {
|
|
11394
|
-
...entry.data,
|
|
11395
|
-
facts: entry.data.facts.map(portableFact),
|
|
11396
|
-
signals: Object.fromEntries(Object.entries(entry.data.signals).map(([name, signal]) => [name, portableSignal(signal)])),
|
|
11397
|
-
consequences: entry.data.consequences.map((consequence) => ({
|
|
11398
|
-
...consequence,
|
|
11399
|
-
fact: portableFact(consequence.fact)
|
|
11400
|
-
})),
|
|
11401
|
-
verification: entry.data.verification.map((verification) => ({
|
|
11402
|
-
...verification,
|
|
11403
|
-
availableChecks: portableRefs(verification.availableChecks)
|
|
11404
|
-
}))
|
|
11405
|
-
}
|
|
11406
|
-
}));
|
|
11407
|
-
clone.progress = {
|
|
11408
|
-
...clone.progress,
|
|
11409
|
-
blockers: clone.progress.blockers.map((blocker) => ({
|
|
11410
|
-
...blocker,
|
|
11411
|
-
refs: portableRefs(blocker.refs)
|
|
11412
|
-
}))
|
|
11898
|
+
const stripped = rewriteRecordRefs(task, taskRecordSchema, portableRef);
|
|
11899
|
+
if (typeof stripped !== "object" || stripped === null || Array.isArray(stripped))
|
|
11900
|
+
throw new TypeError("portable task rewrite produced a non-record");
|
|
11901
|
+
const clone = structuredClone(stripped);
|
|
11902
|
+
const demote = (facts, signals, consequences) => {
|
|
11903
|
+
for (const fact of facts)
|
|
11904
|
+
Object.assign(fact, portableFact(fact));
|
|
11905
|
+
for (const name of Object.keys(signals)) {
|
|
11906
|
+
const signal = signals[name];
|
|
11907
|
+
Object.assign(signal, portableSignal(signal));
|
|
11908
|
+
}
|
|
11909
|
+
for (const consequence of consequences)
|
|
11910
|
+
Object.assign(consequence.fact, portableFact(consequence.fact));
|
|
11413
11911
|
};
|
|
11414
|
-
|
|
11415
|
-
|
|
11416
|
-
data: {
|
|
11417
|
-
...entry.data,
|
|
11418
|
-
refs: portableRefs(entry.data.refs),
|
|
11419
|
-
reviewContext: portableRef(entry.data.reviewContext)
|
|
11420
|
-
}
|
|
11421
|
-
}));
|
|
11422
|
-
clone.decisions = clone.decisions.map((entry) => ({
|
|
11423
|
-
...entry,
|
|
11424
|
-
data: {
|
|
11425
|
-
...entry.data,
|
|
11426
|
-
consumption: entry.data.consumption ? portableRef(entry.data.consumption.actionRef) ? {
|
|
11427
|
-
...entry.data.consumption,
|
|
11428
|
-
actionRef: portableRef(entry.data.consumption.actionRef)
|
|
11429
|
-
} : null : null,
|
|
11430
|
-
binding: {
|
|
11431
|
-
...entry.data.binding,
|
|
11432
|
-
contentRefs: portableRefs(entry.data.binding.contentRefs)
|
|
11433
|
-
}
|
|
11434
|
-
}
|
|
11435
|
-
}));
|
|
11436
|
-
clone.findings = clone.findings.map((entry) => ({
|
|
11437
|
-
...entry,
|
|
11438
|
-
data: { ...entry.data, refs: portableRefs(entry.data.refs) }
|
|
11439
|
-
}));
|
|
11912
|
+
for (const entry of clone.assessments)
|
|
11913
|
+
demote(entry.data.facts, entry.data.signals, entry.data.consequences);
|
|
11440
11914
|
clone.candidates = [];
|
|
11441
11915
|
return clone;
|
|
11442
11916
|
};
|
|
@@ -11461,15 +11935,13 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11461
11935
|
allocate(entry.id);
|
|
11462
11936
|
const fresh = (id) => allocate(id);
|
|
11463
11937
|
const provenance = importedProvenance(context);
|
|
11938
|
+
const remap = (ref) => mapRef(ref, ids);
|
|
11464
11939
|
const intent = {
|
|
11465
11940
|
...source.intent,
|
|
11466
11941
|
id: fresh(source.intent.id),
|
|
11467
11942
|
recordedAt: timestamp,
|
|
11468
11943
|
provenance,
|
|
11469
|
-
data:
|
|
11470
|
-
...source.intent.data,
|
|
11471
|
-
authorityRefs: source.intent.data.authorityRefs.map((ref) => mapRef(ref, ids))
|
|
11472
|
-
}
|
|
11944
|
+
data: rewriteRecordRefs(source.intent.data, intentSchema, remap)
|
|
11473
11945
|
};
|
|
11474
11946
|
const assessments = source.assessments.map((entry) => {
|
|
11475
11947
|
const data = entry.data;
|
|
@@ -11478,28 +11950,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11478
11950
|
id: fresh(entry.id),
|
|
11479
11951
|
recordedAt: timestamp,
|
|
11480
11952
|
provenance,
|
|
11481
|
-
data:
|
|
11482
|
-
...data,
|
|
11483
|
-
facts: data.facts.map((fact) => ({
|
|
11484
|
-
...fact,
|
|
11485
|
-
refs: fact.refs.map((ref) => mapRef(ref, ids))
|
|
11486
|
-
})),
|
|
11487
|
-
signals: Object.fromEntries(Object.entries(data.signals).map(([name, signal]) => [
|
|
11488
|
-
name,
|
|
11489
|
-
{ ...signal, refs: signal.refs.map((ref) => mapRef(ref, ids)) }
|
|
11490
|
-
])),
|
|
11491
|
-
consequences: data.consequences.map((consequence) => ({
|
|
11492
|
-
...consequence,
|
|
11493
|
-
fact: {
|
|
11494
|
-
...consequence.fact,
|
|
11495
|
-
refs: consequence.fact.refs.map((ref) => mapRef(ref, ids))
|
|
11496
|
-
}
|
|
11497
|
-
})),
|
|
11498
|
-
verification: data.verification.map((verification) => ({
|
|
11499
|
-
...verification,
|
|
11500
|
-
availableChecks: verification.availableChecks.map((ref) => mapRef(ref, ids))
|
|
11501
|
-
}))
|
|
11502
|
-
}
|
|
11953
|
+
data: rewriteRecordRefs(data, assessmentSchema, remap)
|
|
11503
11954
|
};
|
|
11504
11955
|
});
|
|
11505
11956
|
const evidence = source.evidence.map((entry) => ({
|
|
@@ -11507,11 +11958,7 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11507
11958
|
id: fresh(entry.id),
|
|
11508
11959
|
recordedAt: timestamp,
|
|
11509
11960
|
provenance,
|
|
11510
|
-
data:
|
|
11511
|
-
...entry.data,
|
|
11512
|
-
refs: entry.data.refs.map((ref) => mapRef(ref, ids)),
|
|
11513
|
-
reviewContext: entry.data.reviewContext ? mapRef(entry.data.reviewContext, ids) : null
|
|
11514
|
-
}
|
|
11961
|
+
data: rewriteRecordRefs(entry.data, evidenceSchema, remap)
|
|
11515
11962
|
}));
|
|
11516
11963
|
const decisions = source.decisions.map((entry) => ({
|
|
11517
11964
|
...entry,
|
|
@@ -11519,21 +11966,23 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11519
11966
|
recordedAt: timestamp,
|
|
11520
11967
|
provenance
|
|
11521
11968
|
}));
|
|
11522
|
-
const findings = source.findings.map((entry) =>
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
11529
|
-
|
|
11530
|
-
|
|
11531
|
-
|
|
11532
|
-
|
|
11533
|
-
|
|
11534
|
-
|
|
11535
|
-
|
|
11536
|
-
|
|
11969
|
+
const findings = source.findings.map((entry) => {
|
|
11970
|
+
const data = rewriteRecordRefs(entry.data, findingSchema, remap);
|
|
11971
|
+
return {
|
|
11972
|
+
...entry,
|
|
11973
|
+
id: fresh(entry.id),
|
|
11974
|
+
recordedAt: timestamp,
|
|
11975
|
+
provenance,
|
|
11976
|
+
data: {
|
|
11977
|
+
...data,
|
|
11978
|
+
resolution: data.resolution ? {
|
|
11979
|
+
...data.resolution,
|
|
11980
|
+
evidenceIds: data.resolution.evidenceIds.map((id) => ids.get(id) ?? id),
|
|
11981
|
+
decisionIds: data.resolution.decisionIds.map((id) => ids.get(id) ?? id)
|
|
11982
|
+
} : null
|
|
11983
|
+
}
|
|
11984
|
+
};
|
|
11985
|
+
});
|
|
11537
11986
|
const workers = source.workers.map((entry) => ({
|
|
11538
11987
|
...entry,
|
|
11539
11988
|
id: fresh(entry.id),
|
|
@@ -11576,19 +12025,21 @@ var importedTask = (source, bundle, destinationWorkspaceId, context, timestamp)
|
|
|
11576
12025
|
closure: null,
|
|
11577
12026
|
assessments,
|
|
11578
12027
|
evidence,
|
|
11579
|
-
decisions: decisions.map((entry) =>
|
|
11580
|
-
|
|
11581
|
-
|
|
11582
|
-
...entry
|
|
11583
|
-
|
|
11584
|
-
...
|
|
11585
|
-
|
|
11586
|
-
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
12028
|
+
decisions: decisions.map((entry) => {
|
|
12029
|
+
const data = rewriteRecordRefs(entry.data, decisionSchema, remap);
|
|
12030
|
+
return {
|
|
12031
|
+
...entry,
|
|
12032
|
+
data: {
|
|
12033
|
+
...data,
|
|
12034
|
+
binding: {
|
|
12035
|
+
...data.binding,
|
|
12036
|
+
taskId: "",
|
|
12037
|
+
workspaceId: destinationWorkspaceId
|
|
12038
|
+
},
|
|
12039
|
+
consumption: null
|
|
12040
|
+
}
|
|
12041
|
+
};
|
|
12042
|
+
}),
|
|
11592
12043
|
...actionProgress ? { actionProgress } : {},
|
|
11593
12044
|
findings,
|
|
11594
12045
|
workers,
|
|
@@ -11626,7 +12077,7 @@ class WorkitCore {
|
|
|
11626
12077
|
}
|
|
11627
12078
|
contextRootError() {
|
|
11628
12079
|
try {
|
|
11629
|
-
if (
|
|
12080
|
+
if (realpathSync5(this.context.root) !== this.store.root)
|
|
11630
12081
|
return failure("invalid_input", "operation context root does not match the task store root");
|
|
11631
12082
|
} catch {
|
|
11632
12083
|
return failure("invalid_input", "operation context root cannot be resolved");
|
|
@@ -11671,7 +12122,7 @@ class WorkitCore {
|
|
|
11671
12122
|
activeWorkerBlocker(task, workspace) {
|
|
11672
12123
|
if (workspace.writer)
|
|
11673
12124
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11674
|
-
if (task.workers.some((entry) =>
|
|
12125
|
+
if (task.workers.some((entry) => workerBlocksTransition("close", entry.data.state)))
|
|
11675
12126
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11676
12127
|
return null;
|
|
11677
12128
|
}
|
|
@@ -11917,37 +12368,33 @@ class WorkitCore {
|
|
|
11917
12368
|
const currentCandidate = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
11918
12369
|
if (!currentCandidate.ok)
|
|
11919
12370
|
return currentCandidate;
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
evidence.candidateId ??= currentCandidate.data.id;
|
|
11923
|
-
} else {
|
|
11924
|
-
evidence.beforeCandidateId ??= null;
|
|
11925
|
-
evidence.candidateId ??= null;
|
|
11926
|
-
}
|
|
12371
|
+
const pin = helper && helper.ok ? helper.data.data.assignment.candidateId : null;
|
|
12372
|
+
const bound = resolveBinding(evidence, pin, currentCandidate.data.id);
|
|
11927
12373
|
if (helper?.ok) {
|
|
11928
12374
|
const assignment = helper.data.data.assignment;
|
|
11929
|
-
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment
|
|
12375
|
+
if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || !checkPin(assignment, bound.beforeCandidateId, bound.candidateId) || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
|
|
11930
12376
|
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11931
12377
|
}
|
|
11932
12378
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11933
|
-
for (const candidateId of [
|
|
12379
|
+
for (const candidateId of [bound.beforeCandidateId, bound.candidateId]) {
|
|
11934
12380
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
11935
12381
|
return failure("invalid_input", "evidence candidate is not a captured candidate");
|
|
11936
12382
|
}
|
|
12383
|
+
const boundEvidence = { ...evidence, ...bound };
|
|
11937
12384
|
const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
|
|
11938
12385
|
const candidates = current.candidates.some((item) => item.id === currentCandidate.data.id) ? current.candidates : [...current.candidates, currentCandidate.data];
|
|
11939
12386
|
const entry = {
|
|
11940
12387
|
id: newId(),
|
|
11941
12388
|
recordedAt: mutation.now,
|
|
11942
12389
|
provenance: provenance(this.context, "agent_reported"),
|
|
11943
|
-
data:
|
|
12390
|
+
data: boundEvidence
|
|
11944
12391
|
};
|
|
11945
12392
|
return success(mutation.revision, null, {
|
|
11946
12393
|
...current,
|
|
11947
12394
|
candidates,
|
|
11948
12395
|
evidence: [...current.evidence, entry],
|
|
11949
12396
|
findings: current.findings.map((finding) => {
|
|
11950
|
-
if (finding.data.disposition === "open" ||
|
|
12397
|
+
if (finding.data.disposition === "open" || boundEvidence.result === "skipped")
|
|
11951
12398
|
return finding;
|
|
11952
12399
|
if (finding.data.disposition !== "fixed")
|
|
11953
12400
|
return finding;
|
|
@@ -11956,8 +12403,12 @@ class WorkitCore {
|
|
|
11956
12403
|
item.evidenceId,
|
|
11957
12404
|
item.status
|
|
11958
12405
|
]));
|
|
11959
|
-
const verified = withEntry.evidence.some((item) =>
|
|
11960
|
-
|
|
12406
|
+
const verified = withEntry.evidence.some((item) => findingVerificationPasses(finding.data.candidateId, {
|
|
12407
|
+
kind: item.data.kind,
|
|
12408
|
+
candidateId: item.data.candidateId,
|
|
12409
|
+
status: statuses.get(item.id) ?? "missing"
|
|
12410
|
+
}));
|
|
12411
|
+
const contradicted = boundEvidence.result === "failed" && finding.data.refs.some((left) => boundEvidence.refs.some((right) => sameValue(left, right)));
|
|
11961
12412
|
return verified && !contradicted ? finding : { ...finding, data: { ...finding.data, disposition: "open", resolution: null } };
|
|
11962
12413
|
})
|
|
11963
12414
|
});
|
|
@@ -11972,7 +12423,10 @@ class WorkitCore {
|
|
|
11972
12423
|
observeDecision(request, observation) {
|
|
11973
12424
|
return this.recordDecision(request, observation, true);
|
|
11974
12425
|
}
|
|
11975
|
-
|
|
12426
|
+
observeStandingDecision(request) {
|
|
12427
|
+
return this.recordDecision(request, undefined, true, true);
|
|
12428
|
+
}
|
|
12429
|
+
recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
|
|
11976
12430
|
const root = this.contextRootError();
|
|
11977
12431
|
if (!root.ok)
|
|
11978
12432
|
return root;
|
|
@@ -11987,8 +12441,10 @@ class WorkitCore {
|
|
|
11987
12441
|
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
11988
12442
|
if (stated && !input.binding?.statedChoice)
|
|
11989
12443
|
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
11990
|
-
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
12444
|
+
if (nativeRequired && nativeObservation === undefined && !stated && !standing)
|
|
11991
12445
|
return failure("permission_denied", "native decision observation is required");
|
|
12446
|
+
if (standing && (!input.binding?.standing || input.purpose !== "action" || input.response !== "approved"))
|
|
12447
|
+
return failure("invalid_input", "standing approvals need an approved action binding with a standing rule");
|
|
11992
12448
|
const task = this.store.readTask(input.taskId);
|
|
11993
12449
|
if (!task.ok)
|
|
11994
12450
|
return task;
|
|
@@ -12020,7 +12476,10 @@ class WorkitCore {
|
|
|
12020
12476
|
consumption: null
|
|
12021
12477
|
};
|
|
12022
12478
|
const data = { ...base, digest: decisionDigest(base) };
|
|
12023
|
-
const
|
|
12479
|
+
const standingApproval = standing && !stated ? verifyStandingApproval(this.store.root, task.data, this.context.caller, input.binding) : null;
|
|
12480
|
+
if (standingApproval && !standingApproval.ok)
|
|
12481
|
+
return standingApproval;
|
|
12482
|
+
const native = nativeRequired && !stated && !standingApproval ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
12024
12483
|
observation: nativeObservation,
|
|
12025
12484
|
expected: {
|
|
12026
12485
|
taskId: task.data.id,
|
|
@@ -12051,7 +12510,7 @@ class WorkitCore {
|
|
|
12051
12510
|
const entry = {
|
|
12052
12511
|
id: newId(),
|
|
12053
12512
|
recordedAt: mutation.now,
|
|
12054
|
-
provenance: nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
12513
|
+
provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
12055
12514
|
data
|
|
12056
12515
|
};
|
|
12057
12516
|
return success(mutation.revision, null, {
|
|
@@ -12115,7 +12574,7 @@ class WorkitCore {
|
|
|
12115
12574
|
if (input.action === "record") {
|
|
12116
12575
|
if (helper?.ok) {
|
|
12117
12576
|
const assignment = helper.data.data.assignment;
|
|
12118
|
-
if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || assignment
|
|
12577
|
+
if (!bindingCovers(assignment.scope, input.scope) || !bindingCovers(task.data.intent.data.scope, input.scope) || !checkPin(assignment, null, input.candidateId ?? null) || !refsWithinScope(input.refs, assignment.scope) || !refsWithinScope(input.refs, task.data.intent.data.scope))
|
|
12119
12578
|
return failure("permission_denied", "finding is outside the worker assignment");
|
|
12120
12579
|
}
|
|
12121
12580
|
if (input.candidateId && !task.data.candidates.some((candidate) => candidate.id === input.candidateId))
|
|
@@ -12162,7 +12621,11 @@ class WorkitCore {
|
|
|
12162
12621
|
const evaluations = evaluateEvidence(task.data, current.data);
|
|
12163
12622
|
const verified = evidence.some((entry) => {
|
|
12164
12623
|
const evaluation = evaluations.find((item) => item.evidenceId === entry.id);
|
|
12165
|
-
return
|
|
12624
|
+
return findingVerificationPasses(finding.data.candidateId, {
|
|
12625
|
+
kind: entry.data.kind,
|
|
12626
|
+
candidateId: entry.data.candidateId,
|
|
12627
|
+
status: evaluation?.status ?? "missing"
|
|
12628
|
+
});
|
|
12166
12629
|
});
|
|
12167
12630
|
if (!verified)
|
|
12168
12631
|
return failure("permission_denied", "fixed findings require passing verification evidence");
|
|
@@ -12174,7 +12637,7 @@ class WorkitCore {
|
|
|
12174
12637
|
if (entry.data.result === "missing" || entry.data.result === "skipped")
|
|
12175
12638
|
return false;
|
|
12176
12639
|
const candidateMatches = finding.data.candidateId === null ? entry.data.candidateId === null : entry.data.candidateId === finding.data.candidateId || entry.data.beforeCandidateId === finding.data.candidateId;
|
|
12177
|
-
const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) =>
|
|
12640
|
+
const referenceMatches = finding.data.refs.some((left) => entry.data.refs.some((right) => sameValue(left, right)));
|
|
12178
12641
|
return candidateMatches && (referenceMatches || entry.data.claim === finding.data.claim);
|
|
12179
12642
|
});
|
|
12180
12643
|
if (!relevant)
|
|
@@ -12252,7 +12715,7 @@ class WorkitCore {
|
|
|
12252
12715
|
const workerId = newId();
|
|
12253
12716
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12254
12717
|
taskId: task.data.id,
|
|
12255
|
-
|
|
12718
|
+
expectedRevision: input.expectedRevision,
|
|
12256
12719
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12257
12720
|
now: trustedNow(this.context),
|
|
12258
12721
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12290,7 +12753,7 @@ class WorkitCore {
|
|
|
12290
12753
|
return failure("permission_denied", "worker report is outside the worker assignment");
|
|
12291
12754
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12292
12755
|
taskId: task.data.id,
|
|
12293
|
-
|
|
12756
|
+
expectedRevision: input.expectedRevision,
|
|
12294
12757
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12295
12758
|
now: trustedNow(this.context),
|
|
12296
12759
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12314,24 +12777,25 @@ class WorkitCore {
|
|
|
12314
12777
|
return failure("invalid_transition", "closed task cannot cancel workers");
|
|
12315
12778
|
if (entry.data.state === "unknown")
|
|
12316
12779
|
return failure("recovery_required", "worker state requires recovery");
|
|
12317
|
-
if (entry.data.state === "stopped")
|
|
12318
|
-
|
|
12319
|
-
|
|
12780
|
+
if (entry.data.state === "stopped") {
|
|
12781
|
+
const stopped = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
12782
|
+
return success(task.data.revision, workspace.data.revision, stopped);
|
|
12783
|
+
}
|
|
12320
12784
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12321
12785
|
taskId: task.data.id,
|
|
12322
|
-
|
|
12786
|
+
expectedRevision: input.expectedRevision,
|
|
12323
12787
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12324
12788
|
now: trustedNow(this.context),
|
|
12325
|
-
workspace: (current, mutation) => success(mutation.revision, mutation.revision,
|
|
12789
|
+
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
12790
|
+
...current,
|
|
12791
|
+
writer: current.writer?.owner.workerId === input.workerId ? null : current.writer
|
|
12792
|
+
}),
|
|
12326
12793
|
task: (current, mutation) => success(mutation.revision, null, {
|
|
12327
12794
|
...current,
|
|
12328
12795
|
workers: current.workers.map((candidate) => candidate.id === input.workerId ? {
|
|
12329
12796
|
...candidate,
|
|
12330
12797
|
recordedAt: mutation.now,
|
|
12331
|
-
data: {
|
|
12332
|
-
...candidate.data,
|
|
12333
|
-
state: settles ? "stopped" : "cancelling"
|
|
12334
|
-
}
|
|
12798
|
+
data: { ...candidate.data, state: "stopped" }
|
|
12335
12799
|
} : candidate)
|
|
12336
12800
|
})
|
|
12337
12801
|
});
|
|
@@ -12428,7 +12892,7 @@ class WorkitCore {
|
|
|
12428
12892
|
return attested;
|
|
12429
12893
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12430
12894
|
taskId: task.data.id,
|
|
12431
|
-
|
|
12895
|
+
expectedRevision: input.expectedRevision,
|
|
12432
12896
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12433
12897
|
now: trustedNow(this.context),
|
|
12434
12898
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, current),
|
|
@@ -12594,6 +13058,12 @@ class WorkitCore {
|
|
|
12594
13058
|
if (!sameSession2(helper.data.data.session, this.context))
|
|
12595
13059
|
return failure("permission_denied", "worker session does not match the caller");
|
|
12596
13060
|
}
|
|
13061
|
+
const gate = this.view(task.data);
|
|
13062
|
+
if (gate.ok) {
|
|
13063
|
+
const writeGates = (task.data.policy?.requirements ?? []).filter((requirement) => requirement.before === "write" && gate.data.requirements.some((evaluation) => evaluation.requirementId === requirement.id && (evaluation.status === "unsatisfied" || evaluation.status === "unavailable")));
|
|
13064
|
+
if (writeGates.length)
|
|
13065
|
+
return failure("requirements_unsatisfied", "writer ownership is gated by unsatisfied requirements; record their evidence or an approved limitation waiver first", { requirementIds: writeGates.map((requirement) => requirement.id) });
|
|
13066
|
+
}
|
|
12597
13067
|
const owner = {
|
|
12598
13068
|
taskId: task.data.id,
|
|
12599
13069
|
workerId: helperId,
|
|
@@ -12605,7 +13075,7 @@ class WorkitCore {
|
|
|
12605
13075
|
};
|
|
12606
13076
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12607
13077
|
taskId: task.data.id,
|
|
12608
|
-
|
|
13078
|
+
expectedRevision: input.expectedRevision,
|
|
12609
13079
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12610
13080
|
now: trustedNow(this.context),
|
|
12611
13081
|
workspace: (current, mutation) => success(mutation.revision, mutation.revision, {
|
|
@@ -12636,7 +13106,7 @@ class WorkitCore {
|
|
|
12636
13106
|
}
|
|
12637
13107
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12638
13108
|
taskId: task.data.id,
|
|
12639
|
-
|
|
13109
|
+
expectedRevision: input.expectedRevision,
|
|
12640
13110
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12641
13111
|
now: trustedNow(this.context),
|
|
12642
13112
|
workspace: (currentWorkspace, mutation) => success(mutation.revision, mutation.revision, { ...currentWorkspace, writer: null }),
|
|
@@ -12868,7 +13338,7 @@ class WorkitCore {
|
|
|
12868
13338
|
if (view.task.workspaceId !== view.workspace.id)
|
|
12869
13339
|
return failure("invalid_input", "task and workspace bindings are invalid");
|
|
12870
13340
|
try {
|
|
12871
|
-
if (
|
|
13341
|
+
if (realpathSync5(view.workspace.root) !== this.store.root)
|
|
12872
13342
|
return failure("invalid_input", "task view root does not match the task store");
|
|
12873
13343
|
} catch {
|
|
12874
13344
|
return failure("invalid_input", "task view root cannot be resolved");
|
|
@@ -12885,7 +13355,15 @@ class WorkitCore {
|
|
|
12885
13355
|
if (seen.has(observation.workerId))
|
|
12886
13356
|
return failure("invalid_input", "worker observations must be unique");
|
|
12887
13357
|
seen.add(observation.workerId);
|
|
12888
|
-
|
|
13358
|
+
const freshTask = this.store.readTask(view.task.id);
|
|
13359
|
+
const freshWorkspace = this.store.readWorkspace();
|
|
13360
|
+
if (!freshTask.ok)
|
|
13361
|
+
return freshTask;
|
|
13362
|
+
if (!freshWorkspace.ok)
|
|
13363
|
+
return freshWorkspace;
|
|
13364
|
+
if (!freshWorkspace.data)
|
|
13365
|
+
return failure("not_found", "workspace not found");
|
|
13366
|
+
if (observation.taskId !== view.task.id || observation.expectedRevision !== freshTask.data.revision || observation.expectedWorkspaceRevision !== freshWorkspace.data.revision)
|
|
12889
13367
|
return failure("revision_conflict", "worker observation is stale");
|
|
12890
13368
|
if (!view.task.workers.some((entry) => entry.id === observation.workerId))
|
|
12891
13369
|
return failure("permission_denied", "worker observation is not assigned");
|
|
@@ -12971,19 +13449,26 @@ class WorkitCore {
|
|
|
12971
13449
|
return failure("permission_denied", "imported resume requires native destination approval");
|
|
12972
13450
|
resumeCandidate = current.data.candidate;
|
|
12973
13451
|
}
|
|
12974
|
-
const blocker = this.activeWorkerBlocker(task.data, workspace.data);
|
|
13452
|
+
const blocker = status === "paused" ? workspace.data.writer ? failure("recovery_required", "writer ownership must be released first") : null : this.activeWorkerBlocker(task.data, workspace.data);
|
|
12975
13453
|
if (blocker)
|
|
12976
13454
|
return blocker;
|
|
13455
|
+
let pauseCandidate = null;
|
|
13456
|
+
if (status === "paused") {
|
|
13457
|
+
const captured = captureCandidate(this.store.root, task.data.intent.data.scope, environment());
|
|
13458
|
+
if (!captured.ok)
|
|
13459
|
+
return captured;
|
|
13460
|
+
pauseCandidate = captured.data;
|
|
13461
|
+
}
|
|
12977
13462
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
12978
13463
|
taskId: task.data.id,
|
|
12979
|
-
|
|
13464
|
+
expectedRevision: input.expectedRevision,
|
|
12980
13465
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
12981
13466
|
now: trustedNow(this.context),
|
|
12982
13467
|
workspace: (workspace, context) => success(context.revision, context.revision, workspace),
|
|
12983
13468
|
task: (current, context) => success(context.revision, null, {
|
|
12984
13469
|
...current,
|
|
12985
13470
|
status,
|
|
12986
|
-
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
13471
|
+
candidates: (resumeCandidate ?? pauseCandidate) && !current.candidates.some((candidate) => candidate.id === (resumeCandidate ?? pauseCandidate).id) ? [...current.candidates, resumeCandidate ?? pauseCandidate] : current.candidates,
|
|
12987
13472
|
progress: current.progress,
|
|
12988
13473
|
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
12989
13474
|
})
|
|
@@ -13021,12 +13506,13 @@ class WorkitCore {
|
|
|
13021
13506
|
return workspace;
|
|
13022
13507
|
if (!workspace.data)
|
|
13023
13508
|
return failure("not_found", "workspace not found");
|
|
13024
|
-
|
|
13025
|
-
|
|
13509
|
+
const revisionBlocker = this.activeWorkerBlocker(task.data, workspace.data);
|
|
13510
|
+
if (revisionBlocker)
|
|
13511
|
+
return revisionBlocker;
|
|
13026
13512
|
this.fillRevisions(input, task.data, workspace.data);
|
|
13027
13513
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
13028
13514
|
taskId: task.data.id,
|
|
13029
|
-
|
|
13515
|
+
expectedRevision: input.expectedRevision,
|
|
13030
13516
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
13031
13517
|
now: trustedNow(this.context),
|
|
13032
13518
|
workspace: (workspace, context) => success(context.revision, context.revision, workspace),
|
|
@@ -13082,7 +13568,7 @@ class WorkitCore {
|
|
|
13082
13568
|
return closure;
|
|
13083
13569
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
13084
13570
|
taskId: task.data.id,
|
|
13085
|
-
|
|
13571
|
+
expectedRevision: input.expectedRevision,
|
|
13086
13572
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
13087
13573
|
now: trustedNow(this.context),
|
|
13088
13574
|
workspace: (value, context) => success(context.revision, context.revision, value),
|
|
@@ -13173,7 +13659,7 @@ var cursorCapabilities = (availability = {}) => {
|
|
|
13173
13659
|
}
|
|
13174
13660
|
];
|
|
13175
13661
|
};
|
|
13176
|
-
var
|
|
13662
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13177
13663
|
var eventNames = new Set([
|
|
13178
13664
|
"sessionStart",
|
|
13179
13665
|
"preToolUse",
|
|
@@ -13184,13 +13670,13 @@ var eventNames = new Set([
|
|
|
13184
13670
|
]);
|
|
13185
13671
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
13186
13672
|
var parseCursorHookInput = (value) => {
|
|
13187
|
-
if (!
|
|
13673
|
+
if (!isRecord2(value) || !eventNames.has(value.hook_event_name))
|
|
13188
13674
|
return { ok: false, error: "hook_event_name is required" };
|
|
13189
13675
|
const roots = value.workspace_roots;
|
|
13190
13676
|
if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
|
|
13191
13677
|
return { ok: false, error: "exactly one workspace root is required" };
|
|
13192
13678
|
const root = roots[0];
|
|
13193
|
-
if (!
|
|
13679
|
+
if (!path9.isAbsolute(root) || !existsSync5(root))
|
|
13194
13680
|
return { ok: false, error: "workspace root must be an existing absolute path" };
|
|
13195
13681
|
const event = value.hook_event_name;
|
|
13196
13682
|
const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
|
|
@@ -13331,10 +13817,10 @@ var handleSubagentStop = () => ({});
|
|
|
13331
13817
|
var handleCursorHook = (raw) => {
|
|
13332
13818
|
const parsed = parseCursorHookInput(raw);
|
|
13333
13819
|
if (!parsed.ok) {
|
|
13334
|
-
return
|
|
13820
|
+
return isRecord2(raw) && raw.hook_event_name === "sessionStart" ? {} : deny(parsed.error);
|
|
13335
13821
|
}
|
|
13336
13822
|
const input = parsed.data;
|
|
13337
|
-
const root =
|
|
13823
|
+
const root = realpathSync6(input.workspace_roots[0]);
|
|
13338
13824
|
if (input.hook_event_name === "sessionStart") {
|
|
13339
13825
|
const actor = input.session_id ?? input.conversation_id;
|
|
13340
13826
|
let compact = "";
|
|
@@ -13376,7 +13862,7 @@ var runCursorHook = async () => {
|
|
|
13376
13862
|
const output = handleCursorHook(input);
|
|
13377
13863
|
process.stdout.write(`${JSON.stringify(output)}
|
|
13378
13864
|
`);
|
|
13379
|
-
const event =
|
|
13865
|
+
const event = isRecord2(input) ? input.hook_event_name : undefined;
|
|
13380
13866
|
if (output.permission === "deny" && ["preToolUse", "beforeShellExecution", "subagentStart"].includes(String(event)))
|
|
13381
13867
|
process.exitCode = 2;
|
|
13382
13868
|
} catch {
|