@brainervirus/workit-cursor 2.1.5 → 2.2.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 +3849 -2659
- package/dist/mcp-server.js +1682 -1181
- package/dist/workit-hook.js +3833 -2617
- package/package.json +3 -3
package/dist/mcp-server.js
CHANGED
|
@@ -16785,7 +16785,7 @@ class StdioServerTransport {
|
|
|
16785
16785
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
16786
16786
|
|
|
16787
16787
|
// packages/workit-core/src/core.ts
|
|
16788
|
-
import { spawnSync as
|
|
16788
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
16789
16789
|
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
16790
16790
|
import path11 from "node:path";
|
|
16791
16791
|
|
|
@@ -16865,7 +16865,8 @@ var hostSchema = _enum([
|
|
|
16865
16865
|
"codex_cli",
|
|
16866
16866
|
"codex_desktop",
|
|
16867
16867
|
"pi",
|
|
16868
|
-
"workit_cli"
|
|
16868
|
+
"workit_cli",
|
|
16869
|
+
"claude_code"
|
|
16869
16870
|
]);
|
|
16870
16871
|
var assuranceSchema = _enum(["enforced", "agent_guided", "unavailable"]);
|
|
16871
16872
|
var outcomeSchema = _enum(["verified", "accepted_limitations", "stopped"]);
|
|
@@ -17315,15 +17316,70 @@ var taskRecordSchema = object2({
|
|
|
17315
17316
|
decisions: array(entrySchema(decisionSchema)),
|
|
17316
17317
|
actionProgress: actionProgressListSchema.optional(),
|
|
17317
17318
|
findings: array(entrySchema(findingSchema)),
|
|
17318
|
-
workers: array(entrySchema(workerSchema))
|
|
17319
|
+
workers: array(entrySchema(workerSchema)),
|
|
17320
|
+
critical: array(nonEmpty).optional()
|
|
17319
17321
|
}).strict();
|
|
17322
|
+
var stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0);
|
|
17323
|
+
var strippable = (issues, prefix = []) => {
|
|
17324
|
+
const strips = [];
|
|
17325
|
+
for (const issue of issues) {
|
|
17326
|
+
if (issue.code === "unrecognized_keys") {
|
|
17327
|
+
strips.push({ path: [...prefix, ...issue.path], keys: issue.keys });
|
|
17328
|
+
continue;
|
|
17329
|
+
}
|
|
17330
|
+
if (issue.code !== "invalid_union")
|
|
17331
|
+
return null;
|
|
17332
|
+
const branch = issue.errors.map((errors) => strippable(errors, [...prefix, ...issue.path])).filter((found) => found !== null && found.length > 0).reduce((best, found) => best === null || stripCount(found) < stripCount(best) ? found : best, null);
|
|
17333
|
+
if (!branch)
|
|
17334
|
+
return null;
|
|
17335
|
+
strips.push(...branch);
|
|
17336
|
+
}
|
|
17337
|
+
return strips;
|
|
17338
|
+
};
|
|
17339
|
+
var recordPath = (path) => path.map((key) => typeof key === "number" ? "*" : String(key)).join(".");
|
|
17340
|
+
var overlaps = (left, right) => left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
|
|
17341
|
+
var parseStoredRecord = (schema, value) => {
|
|
17342
|
+
let parsed = schema.safeParse(value);
|
|
17343
|
+
if (parsed.success)
|
|
17344
|
+
return { success: true, data: parsed.data, stripped: [] };
|
|
17345
|
+
const first = { success: false, error: parsed.error, critical: [] };
|
|
17346
|
+
const declared = typeof value === "object" && value !== null && Array.isArray(value.critical) ? value.critical.filter((item) => typeof item === "string").map((item) => item.split(".").map((key) => /^\d+$/.test(key) ? "*" : key).join(".")) : [];
|
|
17347
|
+
const stripped = [];
|
|
17348
|
+
let current = structuredClone(value);
|
|
17349
|
+
for (let round = 0;round < 8 && !parsed.success; round++) {
|
|
17350
|
+
const strips = strippable(parsed.error.issues);
|
|
17351
|
+
if (strips === null || strips.length === 0)
|
|
17352
|
+
return first;
|
|
17353
|
+
for (const strip of strips) {
|
|
17354
|
+
let target = current;
|
|
17355
|
+
for (const key of strip.path)
|
|
17356
|
+
target = typeof target === "object" && target !== null ? target[key] : undefined;
|
|
17357
|
+
if (typeof target !== "object" || target === null)
|
|
17358
|
+
return first;
|
|
17359
|
+
for (const key of strip.keys) {
|
|
17360
|
+
stripped.push(recordPath([...strip.path, key]));
|
|
17361
|
+
delete target[key];
|
|
17362
|
+
}
|
|
17363
|
+
}
|
|
17364
|
+
parsed = schema.safeParse(current);
|
|
17365
|
+
}
|
|
17366
|
+
if (!parsed.success)
|
|
17367
|
+
return first;
|
|
17368
|
+
const critical = [
|
|
17369
|
+
...new Set(stripped.filter((item) => declared.some((path) => overlaps(item, path))))
|
|
17370
|
+
];
|
|
17371
|
+
if (critical.length > 0)
|
|
17372
|
+
return { ...first, critical };
|
|
17373
|
+
return { success: true, data: parsed.data, stripped: [...new Set(stripped)] };
|
|
17374
|
+
};
|
|
17320
17375
|
var workspaceRecordSchema = object2({
|
|
17321
17376
|
schemaVersion: literal(1),
|
|
17322
17377
|
id,
|
|
17323
17378
|
revision,
|
|
17324
17379
|
root: nonEmpty,
|
|
17325
17380
|
runtime: runtimeSchema.optional(),
|
|
17326
|
-
writer: object2({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
17381
|
+
writer: object2({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable(),
|
|
17382
|
+
critical: array(nonEmpty).optional()
|
|
17327
17383
|
}).strict();
|
|
17328
17384
|
var capabilitySchema = object2({
|
|
17329
17385
|
name: nonEmpty,
|
|
@@ -17561,6 +17617,11 @@ var operationSchemas = {
|
|
|
17561
17617
|
writer: discriminatedUnion("action", Object.values(writerOperations)),
|
|
17562
17618
|
state: discriminatedUnion("action", Object.values(stateOperations))
|
|
17563
17619
|
};
|
|
17620
|
+
var { recover: _unadvertisedRecover, ...advertisedStateOperations } = stateOperations;
|
|
17621
|
+
var advertisedOperationSchemas = {
|
|
17622
|
+
...operationSchemas,
|
|
17623
|
+
state: discriminatedUnion("action", Object.values(advertisedStateOperations))
|
|
17624
|
+
};
|
|
17564
17625
|
var compiledOperationSchemas = Object.fromEntries(OPERATION_FAMILIES.map((family) => [family, compile(operationSchemas[family])]));
|
|
17565
17626
|
var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
17566
17627
|
ok: true,
|
|
@@ -17584,7 +17645,7 @@ function parseOperation(family, input) {
|
|
|
17584
17645
|
return failure2("invalid_input", "operation input is invalid", { fields });
|
|
17585
17646
|
}
|
|
17586
17647
|
function operationJsonSchema(family) {
|
|
17587
|
-
return toJSONSchema(
|
|
17648
|
+
return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
|
|
17588
17649
|
}
|
|
17589
17650
|
var OPERATION_SCHEMA_DEPTH = 1;
|
|
17590
17651
|
var schemaShapeDescription = (value, depth = 0) => {
|
|
@@ -17776,8 +17837,7 @@ function requirementId(input) {
|
|
|
17776
17837
|
// packages/workit-core/src/core/task-store.ts
|
|
17777
17838
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
17778
17839
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
17779
|
-
import * as
|
|
17780
|
-
import { hostname } from "node:os";
|
|
17840
|
+
import * as fs5 from "node:fs";
|
|
17781
17841
|
import path4 from "node:path";
|
|
17782
17842
|
|
|
17783
17843
|
// packages/workit-core/src/core/package-root.ts
|
|
@@ -18400,13 +18460,10 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
18400
18460
|
}
|
|
18401
18461
|
}
|
|
18402
18462
|
}
|
|
18403
|
-
// packages/workit-core/src/core/
|
|
18404
|
-
|
|
18405
|
-
|
|
18406
|
-
|
|
18407
|
-
processStart: string2().nullable(),
|
|
18408
|
-
ownerDigest: string2().regex(/^[0-9a-f]{64}$/).nullable()
|
|
18409
|
-
}).strict();
|
|
18463
|
+
// packages/workit-core/src/core/store-lock.ts
|
|
18464
|
+
import { spawnSync } from "node:child_process";
|
|
18465
|
+
import fs4 from "node:fs";
|
|
18466
|
+
import { hostname } from "node:os";
|
|
18410
18467
|
var metadataLockSchema = object2({
|
|
18411
18468
|
pid: number2().int().nonnegative().safe(),
|
|
18412
18469
|
processStart: string2().nullable(),
|
|
@@ -18414,6 +18471,143 @@ var metadataLockSchema = object2({
|
|
|
18414
18471
|
nonce: string2().min(1),
|
|
18415
18472
|
externalAction: literal(true).optional()
|
|
18416
18473
|
}).strict();
|
|
18474
|
+
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
18475
|
+
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
18476
|
+
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
18477
|
+
var defaultLockTimeoutMs = 250;
|
|
18478
|
+
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
18479
|
+
var parseMetadataLock = (raw) => {
|
|
18480
|
+
let value;
|
|
18481
|
+
try {
|
|
18482
|
+
value = JSON.parse(raw);
|
|
18483
|
+
} catch {
|
|
18484
|
+
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
18485
|
+
}
|
|
18486
|
+
const parsed = metadataLockSchema.safeParse(value);
|
|
18487
|
+
if (!parsed.success)
|
|
18488
|
+
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
18489
|
+
return parsed.data;
|
|
18490
|
+
};
|
|
18491
|
+
var parseMetadataLockOrNull = (raw) => {
|
|
18492
|
+
try {
|
|
18493
|
+
return parseMetadataLock(raw);
|
|
18494
|
+
} catch {
|
|
18495
|
+
return null;
|
|
18496
|
+
}
|
|
18497
|
+
};
|
|
18498
|
+
var sameMetadataLock = (left, right) => {
|
|
18499
|
+
const parsed = metadataLockSchema.safeParse(left);
|
|
18500
|
+
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
18501
|
+
};
|
|
18502
|
+
var parseProcStatStart = (stat) => {
|
|
18503
|
+
const close = stat.lastIndexOf(")");
|
|
18504
|
+
if (close < 0)
|
|
18505
|
+
return null;
|
|
18506
|
+
return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
|
|
18507
|
+
};
|
|
18508
|
+
var processStartOf = (pid) => {
|
|
18509
|
+
if (process.platform === "linux") {
|
|
18510
|
+
try {
|
|
18511
|
+
return parseProcStatStart(fs4.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
18512
|
+
} catch {
|
|
18513
|
+
return null;
|
|
18514
|
+
}
|
|
18515
|
+
}
|
|
18516
|
+
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
18517
|
+
try {
|
|
18518
|
+
const run = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
18519
|
+
encoding: "utf8",
|
|
18520
|
+
timeout: 1000
|
|
18521
|
+
});
|
|
18522
|
+
const value = run.status === 0 ? run.stdout.trim() : "";
|
|
18523
|
+
return value || null;
|
|
18524
|
+
} catch {
|
|
18525
|
+
return null;
|
|
18526
|
+
}
|
|
18527
|
+
}
|
|
18528
|
+
return null;
|
|
18529
|
+
};
|
|
18530
|
+
var readTrimmed = (file) => {
|
|
18531
|
+
try {
|
|
18532
|
+
return fs4.readFileSync(file, "utf8").trim() || null;
|
|
18533
|
+
} catch {
|
|
18534
|
+
return null;
|
|
18535
|
+
}
|
|
18536
|
+
};
|
|
18537
|
+
var cachedLockHost = null;
|
|
18538
|
+
var localLockHost = () => {
|
|
18539
|
+
if (cachedLockHost !== null)
|
|
18540
|
+
return cachedLockHost;
|
|
18541
|
+
let pidns = null;
|
|
18542
|
+
try {
|
|
18543
|
+
pidns = /\[(\d+)\]/.exec(fs4.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
18544
|
+
} catch {}
|
|
18545
|
+
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
18546
|
+
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
18547
|
+
return cachedLockHost;
|
|
18548
|
+
};
|
|
18549
|
+
var pidAlive = (pid) => {
|
|
18550
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
18551
|
+
return false;
|
|
18552
|
+
try {
|
|
18553
|
+
process.kill(pid, 0);
|
|
18554
|
+
return true;
|
|
18555
|
+
} catch (error) {
|
|
18556
|
+
return error.code === "EPERM";
|
|
18557
|
+
}
|
|
18558
|
+
};
|
|
18559
|
+
var classifyLockOwner = (payload, ageMs, localHost = localLockHost()) => {
|
|
18560
|
+
const lock = metadataLockSchema.safeParse(payload);
|
|
18561
|
+
if (!lock.success)
|
|
18562
|
+
return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
|
|
18563
|
+
const { pid, processStart, host } = lock.data;
|
|
18564
|
+
const [lockName, lockSpace] = host.split("#");
|
|
18565
|
+
const [localName, localSpace] = localHost.split("#");
|
|
18566
|
+
if (lockSpace && localSpace && lockName === localName) {
|
|
18567
|
+
const [lockNs, lockBoot] = lockSpace.split(":");
|
|
18568
|
+
const [localNs, localBoot] = localSpace.split(":");
|
|
18569
|
+
if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
|
|
18570
|
+
return { state: "stale", reason: "lock was taken before this machine rebooted" };
|
|
18571
|
+
}
|
|
18572
|
+
if (host !== localHost)
|
|
18573
|
+
return ageMs !== null && ageMs > FOREIGN_LOCK_TTL_MS ? { state: "stale", reason: `lock from ${host} is older than its TTL` } : { state: "unknown", reason: `lock is held from ${host}; its pid cannot be checked here` };
|
|
18574
|
+
if (!pidAlive(pid))
|
|
18575
|
+
return { state: "stale", reason: `pid ${pid} is not running` };
|
|
18576
|
+
const currentStart = processStartOf(pid);
|
|
18577
|
+
if (processStart !== null && currentStart !== null && processStart !== currentStart)
|
|
18578
|
+
return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
|
|
18579
|
+
return { state: "live", reason: `held by running pid ${pid}` };
|
|
18580
|
+
};
|
|
18581
|
+
var ageOf = (file, nowMs) => {
|
|
18582
|
+
try {
|
|
18583
|
+
return nowMs - fs4.lstatSync(file).mtimeMs;
|
|
18584
|
+
} catch {
|
|
18585
|
+
return null;
|
|
18586
|
+
}
|
|
18587
|
+
};
|
|
18588
|
+
var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
18589
|
+
const guard = `${lockPath}.reclaim`;
|
|
18590
|
+
const age = ageOf(guard, nowMs);
|
|
18591
|
+
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
18592
|
+
return false;
|
|
18593
|
+
try {
|
|
18594
|
+
fs4.rmdirSync(guard);
|
|
18595
|
+
return true;
|
|
18596
|
+
} catch {
|
|
18597
|
+
return false;
|
|
18598
|
+
}
|
|
18599
|
+
};
|
|
18600
|
+
|
|
18601
|
+
// packages/workit-core/src/core/task-store.ts
|
|
18602
|
+
var RECOVERY_COPIES_PER_RECORD = 3;
|
|
18603
|
+
var STALE_TEMPORARY_MS = 60 * 60000;
|
|
18604
|
+
var RECOVERY_NAME = /^(task|workspace)\.([^.]+)\.([0-9a-f]{64})\.json$/;
|
|
18605
|
+
var processEvidenceSchema = object2({
|
|
18606
|
+
state: _enum(["stopped", "accounted_for"]),
|
|
18607
|
+
pid: number2().int().nonnegative(),
|
|
18608
|
+
processStart: string2().nullable(),
|
|
18609
|
+
ownerDigest: string2().regex(/^[0-9a-f]{64}$/).nullable()
|
|
18610
|
+
}).strict();
|
|
18417
18611
|
var INDEX_VERSION = 1;
|
|
18418
18612
|
var hostSession = (value) => isObject2(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
18419
18613
|
var indexEntry = (task, file) => {
|
|
@@ -18441,7 +18635,7 @@ var indexEntry = (task, file) => {
|
|
|
18441
18635
|
};
|
|
18442
18636
|
var fileSignature = (file) => {
|
|
18443
18637
|
try {
|
|
18444
|
-
const stat =
|
|
18638
|
+
const stat = fs5.statSync(file, { bigint: true });
|
|
18445
18639
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
18446
18640
|
} catch {
|
|
18447
18641
|
return null;
|
|
@@ -18458,7 +18652,7 @@ var cachedRuntimeVersion = null;
|
|
|
18458
18652
|
var runtimeVersion = () => {
|
|
18459
18653
|
if (cachedRuntimeVersion === null) {
|
|
18460
18654
|
try {
|
|
18461
|
-
const pkg = JSON.parse(
|
|
18655
|
+
const pkg = JSON.parse(fs5.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
18462
18656
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
18463
18657
|
} catch {
|
|
18464
18658
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -18487,36 +18681,20 @@ var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
|
18487
18681
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
18488
18682
|
var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
18489
18683
|
var validDigest = (value) => /^[0-9a-f]{64}$/.test(value);
|
|
18490
|
-
var parseMetadataLock = (raw) => {
|
|
18491
|
-
let value;
|
|
18492
|
-
try {
|
|
18493
|
-
value = JSON.parse(raw);
|
|
18494
|
-
} catch {
|
|
18495
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
18496
|
-
}
|
|
18497
|
-
const parsed = metadataLockSchema.safeParse(value);
|
|
18498
|
-
if (!parsed.success)
|
|
18499
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
18500
|
-
return parsed.data;
|
|
18501
|
-
};
|
|
18502
|
-
var sameMetadataLock = (left, right) => {
|
|
18503
|
-
const parsed = metadataLockSchema.safeParse(left);
|
|
18504
|
-
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
18505
|
-
};
|
|
18506
18684
|
var sameDirectoryIdentity = (left, right) => {
|
|
18507
18685
|
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
18508
18686
|
return false;
|
|
18509
18687
|
const normalizedLeft = path4.resolve(left);
|
|
18510
18688
|
const normalizedRight = path4.resolve(right);
|
|
18511
18689
|
try {
|
|
18512
|
-
const a =
|
|
18513
|
-
const b =
|
|
18690
|
+
const a = fs5.statSync(normalizedLeft, { bigint: true });
|
|
18691
|
+
const b = fs5.statSync(normalizedRight, { bigint: true });
|
|
18514
18692
|
if (!a.isDirectory() || !b.isDirectory())
|
|
18515
18693
|
return false;
|
|
18516
18694
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
18517
18695
|
return true;
|
|
18518
|
-
const canonicalLeft =
|
|
18519
|
-
const canonicalRight =
|
|
18696
|
+
const canonicalLeft = fs5.realpathSync(normalizedLeft);
|
|
18697
|
+
const canonicalRight = fs5.realpathSync(normalizedRight);
|
|
18520
18698
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
18521
18699
|
return true;
|
|
18522
18700
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -18524,12 +18702,47 @@ var sameDirectoryIdentity = (left, right) => {
|
|
|
18524
18702
|
return false;
|
|
18525
18703
|
}
|
|
18526
18704
|
};
|
|
18705
|
+
var TRANSIENT_WINDOWS_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
18706
|
+
var isTransientWindowsError = (error) => {
|
|
18707
|
+
if (process.platform !== "win32")
|
|
18708
|
+
return false;
|
|
18709
|
+
const code = error?.code;
|
|
18710
|
+
return typeof code === "string" && TRANSIENT_WINDOWS_CODES.has(code);
|
|
18711
|
+
};
|
|
18712
|
+
var retryTransient = (run) => {
|
|
18713
|
+
if (process.platform !== "win32")
|
|
18714
|
+
return run();
|
|
18715
|
+
for (let attempt = 0;; attempt += 1) {
|
|
18716
|
+
try {
|
|
18717
|
+
return run();
|
|
18718
|
+
} catch (error) {
|
|
18719
|
+
if (attempt >= 20 || !isTransientWindowsError(error))
|
|
18720
|
+
throw error;
|
|
18721
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5 * (attempt + 1));
|
|
18722
|
+
}
|
|
18723
|
+
}
|
|
18724
|
+
};
|
|
18527
18725
|
var externalActionLockRoots = new AsyncLocalStorage;
|
|
18726
|
+
var heldInProcess = new Map;
|
|
18727
|
+
var holdRoot = (root) => heldInProcess.set(root, (heldInProcess.get(root) ?? 0) + 1);
|
|
18728
|
+
var dropRoot = (root) => {
|
|
18729
|
+
const count = (heldInProcess.get(root) ?? 1) - 1;
|
|
18730
|
+
if (count > 0)
|
|
18731
|
+
heldInProcess.set(root, count);
|
|
18732
|
+
else
|
|
18733
|
+
heldInProcess.delete(root);
|
|
18734
|
+
};
|
|
18735
|
+
var dedupeCandidates = (candidates) => {
|
|
18736
|
+
const last = new Map(candidates.map((candidate, index) => [candidate.id, index]));
|
|
18737
|
+
return candidates.filter((candidate, index) => last.get(candidate.id) === index);
|
|
18738
|
+
};
|
|
18528
18739
|
|
|
18529
18740
|
class TaskStore {
|
|
18530
18741
|
root;
|
|
18531
|
-
|
|
18532
|
-
|
|
18742
|
+
lockTimeoutMs;
|
|
18743
|
+
constructor(root, options = {}) {
|
|
18744
|
+
this.root = fs5.existsSync(root) ? fs5.realpathSync(root) : path4.resolve(root);
|
|
18745
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
18533
18746
|
}
|
|
18534
18747
|
readTask(taskId) {
|
|
18535
18748
|
if (!validId(taskId))
|
|
@@ -18549,11 +18762,11 @@ class TaskStore {
|
|
|
18549
18762
|
return result.result;
|
|
18550
18763
|
}
|
|
18551
18764
|
listTasks() {
|
|
18552
|
-
if (!
|
|
18765
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
18553
18766
|
return success(null, null, []);
|
|
18554
18767
|
let names;
|
|
18555
18768
|
try {
|
|
18556
|
-
names =
|
|
18769
|
+
names = fs5.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
18557
18770
|
} catch (error) {
|
|
18558
18771
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
18559
18772
|
path: this.tasksDir
|
|
@@ -18582,11 +18795,11 @@ class TaskStore {
|
|
|
18582
18795
|
return success(null, null, tasks);
|
|
18583
18796
|
}
|
|
18584
18797
|
listTaskIndex() {
|
|
18585
|
-
if (!
|
|
18798
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
18586
18799
|
return success(null, null, []);
|
|
18587
18800
|
let names;
|
|
18588
18801
|
try {
|
|
18589
|
-
names =
|
|
18802
|
+
names = fs5.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
18590
18803
|
} catch (error) {
|
|
18591
18804
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
18592
18805
|
path: this.tasksDir
|
|
@@ -18954,12 +19167,12 @@ class TaskStore {
|
|
|
18954
19167
|
});
|
|
18955
19168
|
}
|
|
18956
19169
|
recoveryCandidates() {
|
|
18957
|
-
if (!
|
|
19170
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
18958
19171
|
return success(null, null, []);
|
|
18959
19172
|
try {
|
|
18960
19173
|
const candidates = [];
|
|
18961
|
-
for (const name of
|
|
18962
|
-
const match =
|
|
19174
|
+
for (const name of fs5.readdirSync(this.recoveryDir)) {
|
|
19175
|
+
const match = RECOVERY_NAME.exec(name);
|
|
18963
19176
|
if (match)
|
|
18964
19177
|
candidates.push({
|
|
18965
19178
|
target: match[1],
|
|
@@ -19007,7 +19220,7 @@ class TaskStore {
|
|
|
19007
19220
|
return failure2("recovery_required", "validated recovery snapshot not found");
|
|
19008
19221
|
selectedBytes = candidate;
|
|
19009
19222
|
}
|
|
19010
|
-
const selectedRecord = target === "workspace" ? this.parseBytes(selectedBytes, workspaceRecordSchema) : this.parseBytes(selectedBytes, taskRecordSchema);
|
|
19223
|
+
const selectedRecord = target === "workspace" ? this.parseBytes(selectedBytes, workspaceRecordSchema, "rewrite") : this.parseBytes(selectedBytes, taskRecordSchema, "rewrite");
|
|
19011
19224
|
if (!selectedRecord.ok)
|
|
19012
19225
|
return selectedRecord;
|
|
19013
19226
|
if (target === "workspace") {
|
|
@@ -19053,14 +19266,14 @@ class TaskStore {
|
|
|
19053
19266
|
return this.conflict(input.expectedWorkspaceRevision, currentWorkspace.data.revision);
|
|
19054
19267
|
}
|
|
19055
19268
|
if (target === "workspace") {
|
|
19056
|
-
const parsed = this.parseBytes(selectedBytes, workspaceRecordSchema);
|
|
19269
|
+
const parsed = this.parseBytes(selectedBytes, workspaceRecordSchema, "rewrite");
|
|
19057
19270
|
if (!parsed.ok)
|
|
19058
19271
|
return parsed;
|
|
19059
19272
|
const value = { ...parsed.data, revision: newRevision(), writer: null };
|
|
19060
19273
|
const replaced = this.replaceSnapshot(file, value, reacquiredBytes);
|
|
19061
19274
|
return replaced.ok ? success(value.revision, value.revision, value) : replaced;
|
|
19062
19275
|
}
|
|
19063
|
-
const parsed = this.parseBytes(selectedBytes, taskRecordSchema);
|
|
19276
|
+
const parsed = this.parseBytes(selectedBytes, taskRecordSchema, "rewrite");
|
|
19064
19277
|
if (!parsed.ok)
|
|
19065
19278
|
return parsed;
|
|
19066
19279
|
const value = {
|
|
@@ -19071,7 +19284,7 @@ class TaskStore {
|
|
|
19071
19284
|
};
|
|
19072
19285
|
const replaced = this.replaceSnapshot(file, value, reacquiredBytes);
|
|
19073
19286
|
return replaced.ok ? success(value.revision, null, value) : replaced;
|
|
19074
|
-
}, this.recoveryLockOptions(lock.data, evidence.data, recoveryGate));
|
|
19287
|
+
}, this.recoveryLockOptions(lock.data, evidence.data, recoveryGate), "recovery_required");
|
|
19075
19288
|
return result;
|
|
19076
19289
|
} catch (error) {
|
|
19077
19290
|
return failure2("recovery_required", `recovery protocol failed: ${String(error)}`);
|
|
@@ -19106,12 +19319,12 @@ class TaskStore {
|
|
|
19106
19319
|
}
|
|
19107
19320
|
readLockSnapshot() {
|
|
19108
19321
|
try {
|
|
19109
|
-
const stat =
|
|
19322
|
+
const stat = fs5.lstatSync(this.lockPath);
|
|
19110
19323
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
19111
19324
|
return failure2("recovery_required", "metadata lock is not a regular file", {
|
|
19112
19325
|
path: this.lockPath
|
|
19113
19326
|
});
|
|
19114
|
-
const raw =
|
|
19327
|
+
const raw = fs5.readFileSync(this.lockPath, "utf8");
|
|
19115
19328
|
return success(null, null, { raw, data: parseMetadataLock(raw) });
|
|
19116
19329
|
} catch (error) {
|
|
19117
19330
|
if (error?.code === "ENOENT")
|
|
@@ -19122,22 +19335,50 @@ class TaskStore {
|
|
|
19122
19335
|
}
|
|
19123
19336
|
}
|
|
19124
19337
|
metadataLockOptions() {
|
|
19338
|
+
const inProcess = heldInProcess.has(this.root);
|
|
19125
19339
|
return {
|
|
19126
19340
|
lockPath: this.lockPath,
|
|
19127
19341
|
staleMs: Number.MAX_SAFE_INTEGER,
|
|
19128
|
-
timeoutMs: 0,
|
|
19129
|
-
retry: { retries: 0 },
|
|
19130
|
-
staleRecovery: "
|
|
19131
|
-
shouldReclaim: () =>
|
|
19132
|
-
|
|
19342
|
+
timeoutMs: inProcess ? 0 : this.lockTimeoutMs,
|
|
19343
|
+
retry: inProcess ? { retries: 0 } : { minTimeout: 5, maxTimeout: 100, factor: 1.5, randomize: true },
|
|
19344
|
+
staleRecovery: "remove-if-unchanged",
|
|
19345
|
+
shouldReclaim: ({ payload, nowMs }) => {
|
|
19346
|
+
let ageMs = null;
|
|
19347
|
+
try {
|
|
19348
|
+
ageMs = nowMs - fs5.lstatSync(this.lockPath).mtimeMs;
|
|
19349
|
+
} catch {}
|
|
19350
|
+
return classifyLockOwner(payload, ageMs).state === "stale";
|
|
19351
|
+
},
|
|
19352
|
+
shouldRemoveStaleLock: () => true,
|
|
19353
|
+
parsePayload: parseMetadataLockOrNull,
|
|
19133
19354
|
payload: () => ({
|
|
19134
19355
|
pid: process.pid,
|
|
19135
|
-
processStart:
|
|
19136
|
-
host:
|
|
19356
|
+
processStart: processStartOf(process.pid),
|
|
19357
|
+
host: localLockHost(),
|
|
19137
19358
|
nonce: randomUUID2()
|
|
19138
19359
|
})
|
|
19139
19360
|
};
|
|
19140
19361
|
}
|
|
19362
|
+
acquireMetadataLock(options) {
|
|
19363
|
+
clearAbandonedReclaimGuard(this.lockPath);
|
|
19364
|
+
const deadline = Date.now() + (options.timeoutMs ?? 0);
|
|
19365
|
+
while (true) {
|
|
19366
|
+
try {
|
|
19367
|
+
return acquireFileLockSync(this.workspacePath, {
|
|
19368
|
+
...options,
|
|
19369
|
+
timeoutMs: Math.max(0, deadline - Date.now())
|
|
19370
|
+
});
|
|
19371
|
+
} catch (error) {
|
|
19372
|
+
const code = error?.code;
|
|
19373
|
+
if (code !== "file_lock_stale" && !isTransientWindowsError(error) || Date.now() >= deadline)
|
|
19374
|
+
throw error;
|
|
19375
|
+
if (code !== "file_lock_stale" && !this.lockHolderPresent() && !this.lockDirWritable())
|
|
19376
|
+
throw error;
|
|
19377
|
+
if (code !== "file_lock_stale")
|
|
19378
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
19379
|
+
}
|
|
19380
|
+
}
|
|
19381
|
+
}
|
|
19141
19382
|
externalActionLockOptions() {
|
|
19142
19383
|
const options = this.metadataLockOptions();
|
|
19143
19384
|
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
@@ -19155,10 +19396,11 @@ class TaskStore {
|
|
|
19155
19396
|
}
|
|
19156
19397
|
let handle;
|
|
19157
19398
|
try {
|
|
19158
|
-
handle =
|
|
19399
|
+
handle = this.acquireMetadataLock(this.externalActionLockOptions());
|
|
19159
19400
|
} catch (error) {
|
|
19160
19401
|
return this.lockFailure(error);
|
|
19161
19402
|
}
|
|
19403
|
+
holdRoot(this.root);
|
|
19162
19404
|
let result;
|
|
19163
19405
|
try {
|
|
19164
19406
|
if (!handle.verifyStillHeld())
|
|
@@ -19193,8 +19435,9 @@ class TaskStore {
|
|
|
19193
19435
|
path: this.lockPath
|
|
19194
19436
|
});
|
|
19195
19437
|
}
|
|
19438
|
+
dropRoot(this.root);
|
|
19196
19439
|
try {
|
|
19197
|
-
handle.release();
|
|
19440
|
+
retryTransient(() => handle.release());
|
|
19198
19441
|
} catch (error) {
|
|
19199
19442
|
const released = this.lockFailure(error);
|
|
19200
19443
|
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
@@ -19207,6 +19450,9 @@ class TaskStore {
|
|
|
19207
19450
|
}
|
|
19208
19451
|
recoveryLockOptions(observed, evidence, gate) {
|
|
19209
19452
|
const options = this.metadataLockOptions();
|
|
19453
|
+
options.timeoutMs = 0;
|
|
19454
|
+
options.retry = { retries: 0 };
|
|
19455
|
+
options.parsePayload = parseMetadataLock;
|
|
19210
19456
|
options.staleRecovery = "remove-if-unchanged";
|
|
19211
19457
|
options.shouldReclaim = ({ payload }) => Boolean(observed && sameMetadataLock(payload, observed.data) && (evidence.state === "stopped" || evidence.state === "accounted_for"));
|
|
19212
19458
|
options.shouldRemoveStaleLock = ({ raw, payload }) => {
|
|
@@ -19228,7 +19474,7 @@ class TaskStore {
|
|
|
19228
19474
|
};
|
|
19229
19475
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
19230
19476
|
}
|
|
19231
|
-
withLock(operation, options = this.metadataLockOptions()) {
|
|
19477
|
+
withLock(operation, options = this.metadataLockOptions(), contention = "busy") {
|
|
19232
19478
|
try {
|
|
19233
19479
|
this.initializeMutationStorage();
|
|
19234
19480
|
} catch (error) {
|
|
@@ -19239,10 +19485,12 @@ class TaskStore {
|
|
|
19239
19485
|
let handle;
|
|
19240
19486
|
let result = failure2("storage_error", "metadata lock operation did not produce a result");
|
|
19241
19487
|
try {
|
|
19242
|
-
handle =
|
|
19488
|
+
handle = this.acquireMetadataLock(options);
|
|
19243
19489
|
} catch (error) {
|
|
19244
|
-
result = this.lockFailure(error);
|
|
19490
|
+
result = this.lockFailure(error, contention);
|
|
19245
19491
|
}
|
|
19492
|
+
if (handle)
|
|
19493
|
+
holdRoot(this.root);
|
|
19246
19494
|
if (handle) {
|
|
19247
19495
|
try {
|
|
19248
19496
|
if (!handle.verifyStillHeld())
|
|
@@ -19261,8 +19509,9 @@ class TaskStore {
|
|
|
19261
19509
|
}
|
|
19262
19510
|
}
|
|
19263
19511
|
if (handle) {
|
|
19512
|
+
dropRoot(this.root);
|
|
19264
19513
|
try {
|
|
19265
|
-
handle.release();
|
|
19514
|
+
retryTransient(() => handle.release());
|
|
19266
19515
|
} catch (error) {
|
|
19267
19516
|
const releaseFailure = this.lockFailure(error);
|
|
19268
19517
|
const releaseError = releaseFailure.ok ? "metadata lock release failed" : releaseFailure.error;
|
|
@@ -19271,25 +19520,65 @@ class TaskStore {
|
|
|
19271
19520
|
}
|
|
19272
19521
|
return result;
|
|
19273
19522
|
}
|
|
19274
|
-
|
|
19523
|
+
lockDirWritable() {
|
|
19524
|
+
const probe = `${this.lockPath}.${process.pid}.${randomUUID2()}.probe`;
|
|
19525
|
+
try {
|
|
19526
|
+
fs5.closeSync(fs5.openSync(probe, "wx", 384));
|
|
19527
|
+
} catch {
|
|
19528
|
+
return false;
|
|
19529
|
+
}
|
|
19530
|
+
try {
|
|
19531
|
+
fs5.rmSync(probe, { force: true });
|
|
19532
|
+
} catch {}
|
|
19533
|
+
return true;
|
|
19534
|
+
}
|
|
19535
|
+
lockHolderPresent() {
|
|
19536
|
+
try {
|
|
19537
|
+
fs5.lstatSync(this.lockPath);
|
|
19538
|
+
return true;
|
|
19539
|
+
} catch (error) {
|
|
19540
|
+
return error?.code !== "ENOENT";
|
|
19541
|
+
}
|
|
19542
|
+
}
|
|
19543
|
+
lockFailure(error, contention = "busy") {
|
|
19275
19544
|
const value = error;
|
|
19276
19545
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
19277
|
-
if (
|
|
19278
|
-
|
|
19279
|
-
|
|
19546
|
+
if (isTransientWindowsError(error) && !this.lockHolderPresent() && !this.lockDirWritable())
|
|
19547
|
+
return failure2("storage_error", `cannot create the workspace metadata lock (${code}); no other Workit call holds it`, {
|
|
19548
|
+
path: this.lockPath,
|
|
19549
|
+
guidance: `Check permissions and the read-only attribute on ${path4.dirname(this.lockPath)}.`
|
|
19550
|
+
});
|
|
19551
|
+
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
19552
|
+
let lock = null;
|
|
19553
|
+
try {
|
|
19554
|
+
lock = parseMetadataLockOrNull(fs5.readFileSync(this.lockPath, "utf8"));
|
|
19555
|
+
} catch {}
|
|
19556
|
+
if (lock?.externalAction)
|
|
19280
19557
|
return failure2("writer_conflict", "workspace is reserved by a managed external action", {
|
|
19281
19558
|
outcome: "not_started",
|
|
19282
19559
|
path: this.lockPath
|
|
19283
19560
|
});
|
|
19561
|
+
if (contention === "busy")
|
|
19562
|
+
return failure2("busy", `workspace metadata lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
19563
|
+
outcome: "not_started",
|
|
19564
|
+
path: this.lockPath,
|
|
19565
|
+
guidance: "Retry the same call. If it stays busy, run `workit doctor --fix-lock` to clear a lock left by a dead process."
|
|
19566
|
+
});
|
|
19284
19567
|
}
|
|
19285
19568
|
const recovery = code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || code === "metadata_lock_invalid" || code === "not-file" || /metadata lock|file lock|reclaim/i.test(String(value?.message ?? error));
|
|
19286
19569
|
return failure2(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
19287
19570
|
}
|
|
19288
19571
|
initializeMutationStorage() {
|
|
19289
|
-
|
|
19290
|
-
|
|
19291
|
-
|
|
19292
|
-
|
|
19572
|
+
retryTransient(() => fs5.mkdirSync(this.tasksDir, { recursive: true }));
|
|
19573
|
+
retryTransient(() => fs5.mkdirSync(this.recoveryDir, { recursive: true }));
|
|
19574
|
+
let current = null;
|
|
19575
|
+
try {
|
|
19576
|
+
current = retryTransient(() => fs5.readFileSync(this.gitignorePath, "utf8"));
|
|
19577
|
+
} catch {}
|
|
19578
|
+
if (current !== `*
|
|
19579
|
+
`)
|
|
19580
|
+
retryTransient(() => fs5.writeFileSync(this.gitignorePath, `*
|
|
19581
|
+
`));
|
|
19293
19582
|
}
|
|
19294
19583
|
replaceSnapshot(file, value, previous) {
|
|
19295
19584
|
let temporary;
|
|
@@ -19301,14 +19590,14 @@ class TaskStore {
|
|
|
19301
19590
|
else if (typeof previous === "string")
|
|
19302
19591
|
this.saveRecovery(file, previous);
|
|
19303
19592
|
temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
19304
|
-
const fd =
|
|
19593
|
+
const fd = fs5.openSync(temporary, "wx", 384);
|
|
19305
19594
|
try {
|
|
19306
|
-
|
|
19307
|
-
|
|
19595
|
+
fs5.writeSync(fd, jsonBytes(value));
|
|
19596
|
+
fs5.fsyncSync(fd);
|
|
19308
19597
|
} finally {
|
|
19309
|
-
|
|
19598
|
+
fs5.closeSync(fd);
|
|
19310
19599
|
}
|
|
19311
|
-
|
|
19600
|
+
retryTransient(() => fs5.renameSync(temporary, file));
|
|
19312
19601
|
temporary = undefined;
|
|
19313
19602
|
this.fsyncDirectory(path4.dirname(file));
|
|
19314
19603
|
if (path4.dirname(file) === this.tasksDir)
|
|
@@ -19321,13 +19610,13 @@ class TaskStore {
|
|
|
19321
19610
|
} finally {
|
|
19322
19611
|
if (temporary)
|
|
19323
19612
|
try {
|
|
19324
|
-
|
|
19613
|
+
fs5.unlinkSync(temporary);
|
|
19325
19614
|
} catch {}
|
|
19326
19615
|
}
|
|
19327
19616
|
}
|
|
19328
19617
|
readIndex(workspaceId) {
|
|
19329
19618
|
try {
|
|
19330
|
-
const value = JSON.parse(
|
|
19619
|
+
const value = JSON.parse(fs5.readFileSync(this.indexPath, "utf8"));
|
|
19331
19620
|
if (!isObject2(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject2(value.tasks))
|
|
19332
19621
|
return {};
|
|
19333
19622
|
const tasks = {};
|
|
@@ -19342,15 +19631,15 @@ class TaskStore {
|
|
|
19342
19631
|
writeIndex(workspaceId, entries) {
|
|
19343
19632
|
const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
19344
19633
|
try {
|
|
19345
|
-
|
|
19634
|
+
fs5.writeFileSync(temporary, JSON.stringify({
|
|
19346
19635
|
version: INDEX_VERSION,
|
|
19347
19636
|
workspaceId,
|
|
19348
19637
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
19349
19638
|
}), { mode: 384, flag: "wx" });
|
|
19350
|
-
|
|
19639
|
+
retryTransient(() => fs5.renameSync(temporary, this.indexPath));
|
|
19351
19640
|
} catch {
|
|
19352
19641
|
try {
|
|
19353
|
-
|
|
19642
|
+
fs5.unlinkSync(temporary);
|
|
19354
19643
|
} catch {}
|
|
19355
19644
|
}
|
|
19356
19645
|
}
|
|
@@ -19368,33 +19657,153 @@ class TaskStore {
|
|
|
19368
19657
|
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
19369
19658
|
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
19370
19659
|
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
19371
|
-
if (
|
|
19372
|
-
if (digestBytes(
|
|
19373
|
-
|
|
19374
|
-
|
|
19660
|
+
if (fs5.existsSync(destination)) {
|
|
19661
|
+
if (digestBytes(retryTransient(() => fs5.readFileSync(destination))) !== digestBytes(bytes))
|
|
19662
|
+
throw new Error("recovery copy already exists with different bytes");
|
|
19663
|
+
const stamp = new Date;
|
|
19664
|
+
retryTransient(() => fs5.utimesSync(destination, stamp, stamp));
|
|
19665
|
+
this.pruneRecovery(`${target}.${id}.`, destination);
|
|
19666
|
+
return;
|
|
19375
19667
|
}
|
|
19376
19668
|
let temporary;
|
|
19377
19669
|
try {
|
|
19378
19670
|
temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
|
|
19379
|
-
const fd =
|
|
19671
|
+
const fd = fs5.openSync(temporary, "wx", 384);
|
|
19380
19672
|
try {
|
|
19381
|
-
|
|
19382
|
-
|
|
19673
|
+
fs5.writeSync(fd, bytes);
|
|
19674
|
+
fs5.fsyncSync(fd);
|
|
19383
19675
|
} finally {
|
|
19384
|
-
|
|
19676
|
+
fs5.closeSync(fd);
|
|
19385
19677
|
}
|
|
19386
|
-
|
|
19678
|
+
retryTransient(() => fs5.renameSync(temporary, destination));
|
|
19387
19679
|
temporary = undefined;
|
|
19388
19680
|
this.fsyncDirectory(this.recoveryDir);
|
|
19681
|
+
this.pruneRecovery(`${target}.${id}.`, destination);
|
|
19389
19682
|
} finally {
|
|
19390
19683
|
if (temporary)
|
|
19391
19684
|
try {
|
|
19392
|
-
|
|
19685
|
+
fs5.unlinkSync(temporary);
|
|
19393
19686
|
} catch {}
|
|
19394
19687
|
}
|
|
19395
19688
|
}
|
|
19689
|
+
pruneRecovery(prefix, keep) {
|
|
19690
|
+
try {
|
|
19691
|
+
const copies = this.recoveryCopies(prefix);
|
|
19692
|
+
const surplus = this.surplusCopies(copies, path4.basename(keep));
|
|
19693
|
+
for (const copy of surplus)
|
|
19694
|
+
fs5.rmSync(copy.path, { force: true });
|
|
19695
|
+
} catch {}
|
|
19696
|
+
}
|
|
19697
|
+
recoveryCopies(prefix = "") {
|
|
19698
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
19699
|
+
return [];
|
|
19700
|
+
const copies = [];
|
|
19701
|
+
for (const name of fs5.readdirSync(this.recoveryDir)) {
|
|
19702
|
+
if (!name.startsWith(prefix))
|
|
19703
|
+
continue;
|
|
19704
|
+
const match = RECOVERY_NAME.exec(name);
|
|
19705
|
+
if (!match)
|
|
19706
|
+
continue;
|
|
19707
|
+
const file = path4.join(this.recoveryDir, name);
|
|
19708
|
+
try {
|
|
19709
|
+
const stat = fs5.lstatSync(file, { bigint: true });
|
|
19710
|
+
if (!stat.isFile())
|
|
19711
|
+
continue;
|
|
19712
|
+
copies.push({ name, path: file, group: `${match[1]}.${match[2]}`, mtimeNs: stat.mtimeNs });
|
|
19713
|
+
} catch {}
|
|
19714
|
+
}
|
|
19715
|
+
return copies;
|
|
19716
|
+
}
|
|
19717
|
+
surplusCopies(copies, keep) {
|
|
19718
|
+
const ordered = copies.toSorted((left, right) => left.name === keep ? -1 : right.name === keep ? 1 : left.mtimeNs === right.mtimeNs ? left.name.localeCompare(right.name) : left.mtimeNs > right.mtimeNs ? -1 : 1);
|
|
19719
|
+
return ordered.slice(RECOVERY_COPIES_PER_RECORD);
|
|
19720
|
+
}
|
|
19721
|
+
collectGarbage(options = {}) {
|
|
19722
|
+
const dryRun = options.dryRun === true;
|
|
19723
|
+
const report = {
|
|
19724
|
+
dryRun,
|
|
19725
|
+
recovery: { removed: 0, removedBytes: 0, kept: 0 },
|
|
19726
|
+
temporary: { removed: 0 },
|
|
19727
|
+
candidates: { removed: 0, tasks: [], skippedActive: [], skippedClosed: [], failed: [] }
|
|
19728
|
+
};
|
|
19729
|
+
if (!fs5.existsSync(this.workitDir))
|
|
19730
|
+
return success(null, null, report);
|
|
19731
|
+
const sweep = () => {
|
|
19732
|
+
try {
|
|
19733
|
+
const groups = new Map;
|
|
19734
|
+
for (const copy of this.recoveryCopies())
|
|
19735
|
+
groups.set(copy.group, [...groups.get(copy.group) ?? [], copy]);
|
|
19736
|
+
for (const copies of groups.values()) {
|
|
19737
|
+
const surplus = this.surplusCopies(copies);
|
|
19738
|
+
report.recovery.kept += copies.length - surplus.length;
|
|
19739
|
+
for (const copy of surplus) {
|
|
19740
|
+
report.recovery.removed += 1;
|
|
19741
|
+
try {
|
|
19742
|
+
report.recovery.removedBytes += fs5.statSync(copy.path).size;
|
|
19743
|
+
} catch {}
|
|
19744
|
+
if (!dryRun)
|
|
19745
|
+
fs5.rmSync(copy.path, { force: true });
|
|
19746
|
+
}
|
|
19747
|
+
}
|
|
19748
|
+
const nowMs = Date.now();
|
|
19749
|
+
for (const directory of [this.recoveryDir, this.tasksDir, this.workitDir]) {
|
|
19750
|
+
if (!fs5.existsSync(directory))
|
|
19751
|
+
continue;
|
|
19752
|
+
for (const name of fs5.readdirSync(directory)) {
|
|
19753
|
+
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
19754
|
+
continue;
|
|
19755
|
+
const file = path4.join(directory, name);
|
|
19756
|
+
const stat = fs5.lstatSync(file);
|
|
19757
|
+
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
19758
|
+
continue;
|
|
19759
|
+
report.temporary.removed += 1;
|
|
19760
|
+
if (!dryRun)
|
|
19761
|
+
fs5.rmSync(file, { force: true });
|
|
19762
|
+
}
|
|
19763
|
+
}
|
|
19764
|
+
return success(null, null, null);
|
|
19765
|
+
} catch (error) {
|
|
19766
|
+
return failure2("storage_error", `garbage collection failed: ${String(error)}`, {
|
|
19767
|
+
path: this.recoveryDir
|
|
19768
|
+
});
|
|
19769
|
+
}
|
|
19770
|
+
};
|
|
19771
|
+
const pruned = dryRun ? sweep() : this.withLock(sweep);
|
|
19772
|
+
if (!pruned.ok)
|
|
19773
|
+
return pruned;
|
|
19774
|
+
const tasks = this.listTasks();
|
|
19775
|
+
if (!tasks.ok)
|
|
19776
|
+
return tasks;
|
|
19777
|
+
for (const task of tasks.data) {
|
|
19778
|
+
const deduped = dedupeCandidates(task.candidates);
|
|
19779
|
+
const removed = task.candidates.length - deduped.length;
|
|
19780
|
+
if (removed === 0)
|
|
19781
|
+
continue;
|
|
19782
|
+
if (task.status === "active") {
|
|
19783
|
+
report.candidates.skippedActive.push(task.id);
|
|
19784
|
+
continue;
|
|
19785
|
+
}
|
|
19786
|
+
if (task.status === "closed") {
|
|
19787
|
+
report.candidates.skippedClosed.push(task.id);
|
|
19788
|
+
continue;
|
|
19789
|
+
}
|
|
19790
|
+
if (!dryRun) {
|
|
19791
|
+
const written = this.mutateTask(task.id, task.revision, (current) => success(current.revision, null, {
|
|
19792
|
+
...current,
|
|
19793
|
+
candidates: dedupeCandidates(current.candidates)
|
|
19794
|
+
}));
|
|
19795
|
+
if (!written.ok) {
|
|
19796
|
+
report.candidates.failed.push(task.id);
|
|
19797
|
+
continue;
|
|
19798
|
+
}
|
|
19799
|
+
}
|
|
19800
|
+
report.candidates.removed += removed;
|
|
19801
|
+
report.candidates.tasks.push(task.id);
|
|
19802
|
+
}
|
|
19803
|
+
return success(null, null, report);
|
|
19804
|
+
}
|
|
19396
19805
|
findRecovery(target, taskId, digest, workspace) {
|
|
19397
|
-
if (!
|
|
19806
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
19398
19807
|
return null;
|
|
19399
19808
|
const workspaceId = workspace?.id ?? this.trustedWorkspaceId();
|
|
19400
19809
|
if (target === "workspace" && !workspaceId)
|
|
@@ -19406,9 +19815,9 @@ class TaskStore {
|
|
|
19406
19815
|
if (candidate.target !== target || candidate.digest !== digest)
|
|
19407
19816
|
continue;
|
|
19408
19817
|
try {
|
|
19409
|
-
if (!
|
|
19818
|
+
if (!fs5.lstatSync(candidate.path).isFile())
|
|
19410
19819
|
continue;
|
|
19411
|
-
const bytes =
|
|
19820
|
+
const bytes = fs5.readFileSync(candidate.path);
|
|
19412
19821
|
if (digestBytes(bytes) !== digest)
|
|
19413
19822
|
continue;
|
|
19414
19823
|
if (target === "workspace") {
|
|
@@ -19426,11 +19835,11 @@ class TaskStore {
|
|
|
19426
19835
|
}
|
|
19427
19836
|
fsyncDirectory(directory) {
|
|
19428
19837
|
try {
|
|
19429
|
-
const fd =
|
|
19838
|
+
const fd = fs5.openSync(directory, "r");
|
|
19430
19839
|
try {
|
|
19431
|
-
|
|
19840
|
+
fs5.fsyncSync(fd);
|
|
19432
19841
|
} finally {
|
|
19433
|
-
|
|
19842
|
+
fs5.closeSync(fd);
|
|
19434
19843
|
}
|
|
19435
19844
|
} catch (error) {
|
|
19436
19845
|
if (process.platform !== "win32")
|
|
@@ -19438,14 +19847,14 @@ class TaskStore {
|
|
|
19438
19847
|
}
|
|
19439
19848
|
}
|
|
19440
19849
|
trustedWorkspaceId() {
|
|
19441
|
-
if (!
|
|
19850
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
19442
19851
|
return null;
|
|
19443
19852
|
const ids = new Set;
|
|
19444
19853
|
try {
|
|
19445
|
-
for (const name of
|
|
19854
|
+
for (const name of fs5.readdirSync(this.tasksDir)) {
|
|
19446
19855
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
19447
19856
|
return null;
|
|
19448
|
-
const parsed = this.parseBytes(
|
|
19857
|
+
const parsed = this.parseBytes(fs5.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
19449
19858
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
19450
19859
|
return null;
|
|
19451
19860
|
ids.add(parsed.data.workspaceId);
|
|
@@ -19457,14 +19866,14 @@ class TaskStore {
|
|
|
19457
19866
|
}
|
|
19458
19867
|
snapshotBytes(file) {
|
|
19459
19868
|
try {
|
|
19460
|
-
return
|
|
19869
|
+
return retryTransient(() => fs5.readFileSync(file));
|
|
19461
19870
|
} catch {
|
|
19462
19871
|
return null;
|
|
19463
19872
|
}
|
|
19464
19873
|
}
|
|
19465
19874
|
readRecord(file, schema) {
|
|
19466
19875
|
try {
|
|
19467
|
-
const bytes =
|
|
19876
|
+
const bytes = retryTransient(() => fs5.readFileSync(file, "utf8"));
|
|
19468
19877
|
return { exists: true, result: this.parseBytes(bytes, schema) };
|
|
19469
19878
|
} catch (error) {
|
|
19470
19879
|
if (error?.code === "ENOENT")
|
|
@@ -19477,7 +19886,7 @@ class TaskStore {
|
|
|
19477
19886
|
};
|
|
19478
19887
|
}
|
|
19479
19888
|
}
|
|
19480
|
-
parseBytes(bytes, schema) {
|
|
19889
|
+
parseBytes(bytes, schema, mode = "read") {
|
|
19481
19890
|
let value;
|
|
19482
19891
|
try {
|
|
19483
19892
|
value = JSON.parse(typeof bytes === "string" ? bytes : bytes.toString("utf8"));
|
|
@@ -19486,21 +19895,19 @@ class TaskStore {
|
|
|
19486
19895
|
}
|
|
19487
19896
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
19488
19897
|
return failure2("unsupported_version", "unsupported snapshot schema version");
|
|
19489
|
-
const parsed = schema
|
|
19490
|
-
if (parsed.success)
|
|
19898
|
+
const parsed = parseStoredRecord(schema, value);
|
|
19899
|
+
if (parsed.success && (mode === "read" || parsed.stripped.length === 0))
|
|
19491
19900
|
return success(null, null, parsed.data);
|
|
19492
19901
|
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
19902
|
+
const upgrade = `upgrade Workit before ${parsed.success ? "recovering" : "mutating"} this checkout`;
|
|
19903
|
+
if (parsed.success)
|
|
19904
|
+
return failure2("recovery_required", `snapshot carries fields this Workit cannot preserve (${parsed.stripped.join(", ")}); ${upgrade}`);
|
|
19905
|
+
if (parsed.critical.length > 0)
|
|
19906
|
+
return failure2("recovery_required", `snapshot requires fields this Workit cannot read (${parsed.critical.join(", ")}); ${upgrade}`);
|
|
19493
19907
|
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
19494
19908
|
return failure2("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
19495
19909
|
return failure2("recovery_required", "snapshot does not satisfy its schema");
|
|
19496
19910
|
}
|
|
19497
|
-
processStart(pid) {
|
|
19498
|
-
try {
|
|
19499
|
-
return fs4.readFileSync(`/proc/${pid}/stat`, "utf8").split(" ")[21] ?? null;
|
|
19500
|
-
} catch {
|
|
19501
|
-
return null;
|
|
19502
|
-
}
|
|
19503
|
-
}
|
|
19504
19911
|
conflict(expected, actual) {
|
|
19505
19912
|
return failure2("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
19506
19913
|
expectedRevision: expected,
|
|
@@ -19532,55 +19939,118 @@ class TaskStore {
|
|
|
19532
19939
|
return path4.join(this.workitDir, ".gitignore");
|
|
19533
19940
|
}
|
|
19534
19941
|
}
|
|
19535
|
-
// packages/workit-core/src/core/
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19540
|
-
|
|
19541
|
-
|
|
19542
|
-
|
|
19543
|
-
|
|
19544
|
-
|
|
19545
|
-
|
|
19546
|
-
|
|
19547
|
-
|
|
19548
|
-
|
|
19549
|
-
|
|
19550
|
-
|
|
19551
|
-
|
|
19552
|
-
|
|
19553
|
-
|
|
19554
|
-
|
|
19555
|
-
|
|
19556
|
-
|
|
19557
|
-
|
|
19558
|
-
|
|
19559
|
-
|
|
19560
|
-
|
|
19561
|
-
|
|
19562
|
-
|
|
19563
|
-
|
|
19564
|
-
|
|
19565
|
-
|
|
19566
|
-
|
|
19567
|
-
|
|
19568
|
-
|
|
19569
|
-
|
|
19570
|
-
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
|
|
19577
|
-
|
|
19578
|
-
|
|
19579
|
-
|
|
19580
|
-
|
|
19581
|
-
|
|
19582
|
-
|
|
19583
|
-
|
|
19942
|
+
// packages/workit-core/src/core/workers.ts
|
|
19943
|
+
var same = (left, right) => {
|
|
19944
|
+
try {
|
|
19945
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
19946
|
+
} catch {
|
|
19947
|
+
return false;
|
|
19948
|
+
}
|
|
19949
|
+
};
|
|
19950
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
19951
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
19952
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
19953
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
19954
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
19955
|
+
const code = char.charCodeAt(0);
|
|
19956
|
+
return code < 32 || code === 127;
|
|
19957
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
19958
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
19959
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
19960
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
19961
|
+
const owner = workspace.writer?.owner;
|
|
19962
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
19963
|
+
};
|
|
19964
|
+
function assertProductWriteAllowed(input) {
|
|
19965
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
19966
|
+
return failure2("invalid_input", "invalid product write path");
|
|
19967
|
+
if (!input.store)
|
|
19968
|
+
return failure2("permission_denied", "write authorization requires core state");
|
|
19969
|
+
const task = input.store.readTask(input.task.id);
|
|
19970
|
+
if (!task.ok)
|
|
19971
|
+
return task;
|
|
19972
|
+
const workspace = input.store.readWorkspace();
|
|
19973
|
+
if (!workspace.ok)
|
|
19974
|
+
return workspace;
|
|
19975
|
+
if (!workspace.data)
|
|
19976
|
+
return failure2("not_found", "workspace not found");
|
|
19977
|
+
const workspaceRecord = workspace.data;
|
|
19978
|
+
if (task.data.status !== "active")
|
|
19979
|
+
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
19980
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
19981
|
+
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
19982
|
+
const writer = workspaceRecord.writer;
|
|
19983
|
+
if (!writer)
|
|
19984
|
+
return failure2("permission_denied", "checkout has no writer owner");
|
|
19985
|
+
if (writer.state === "uncertain")
|
|
19986
|
+
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
19987
|
+
owner: writer.owner
|
|
19988
|
+
});
|
|
19989
|
+
const caller = callerValue(input.caller);
|
|
19990
|
+
const workerId = workerIdOf(input.caller);
|
|
19991
|
+
const owner = writer.owner;
|
|
19992
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
19993
|
+
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
19994
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
19995
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
19996
|
+
return failure2("permission_denied", "only implementers can own product writes");
|
|
19997
|
+
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
19998
|
+
return failure2("recovery_required", "worker session is not an active writer");
|
|
19999
|
+
return success(null, workspaceRecord.revision, owner);
|
|
20000
|
+
}
|
|
20001
|
+
|
|
20002
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
20003
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
20004
|
+
import * as fs8 from "node:fs";
|
|
20005
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
20006
|
+
import path10 from "node:path";
|
|
20007
|
+
|
|
20008
|
+
// packages/workit-core/src/core/authority.ts
|
|
20009
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
20010
|
+
import * as fs7 from "node:fs";
|
|
20011
|
+
import path9 from "node:path";
|
|
20012
|
+
|
|
20013
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
20014
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
20015
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
20016
|
+
import path8 from "node:path";
|
|
20017
|
+
|
|
20018
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
20019
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
20020
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
20021
|
+
import path6 from "node:path";
|
|
20022
|
+
|
|
20023
|
+
// packages/workit-core/src/core/config.ts
|
|
20024
|
+
import {
|
|
20025
|
+
copyFileSync,
|
|
20026
|
+
cpSync,
|
|
20027
|
+
existsSync as existsSync3,
|
|
20028
|
+
mkdirSync as mkdirSync2,
|
|
20029
|
+
readFileSync as readFileSync2,
|
|
20030
|
+
readdirSync as readdirSync2,
|
|
20031
|
+
writeFileSync as writeFileSync2
|
|
20032
|
+
} from "node:fs";
|
|
20033
|
+
import os from "node:os";
|
|
20034
|
+
import path5 from "node:path";
|
|
20035
|
+
|
|
20036
|
+
// packages/workit-core/src/core/boundary.ts
|
|
20037
|
+
import { statSync as statSync2 } from "node:fs";
|
|
20038
|
+
var EVENT = {
|
|
20039
|
+
initialization: "initialization",
|
|
20040
|
+
provenance: "provenance",
|
|
20041
|
+
assets: "assets",
|
|
20042
|
+
configurationSource: "configuration_source",
|
|
20043
|
+
hooks: "hooks",
|
|
20044
|
+
mcpConnection: "mcp_connection",
|
|
20045
|
+
migration: "migration",
|
|
20046
|
+
installSteps: "install_steps",
|
|
20047
|
+
uncaughtFailure: "uncaught_failure",
|
|
20048
|
+
toolsFailed: "tools_failed",
|
|
20049
|
+
doctor: "doctor"
|
|
20050
|
+
};
|
|
20051
|
+
var errorDetail = (err) => {
|
|
20052
|
+
if (err instanceof Error) {
|
|
20053
|
+
return { error_name: err.name, error: err.message };
|
|
19584
20054
|
}
|
|
19585
20055
|
return { error_name: "unknown", error: String(err) };
|
|
19586
20056
|
};
|
|
@@ -20207,13 +20677,13 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
20207
20677
|
};
|
|
20208
20678
|
|
|
20209
20679
|
// packages/workit-core/src/core/vcs-config.ts
|
|
20210
|
-
import
|
|
20680
|
+
import fs6 from "node:fs";
|
|
20211
20681
|
import path7 from "node:path";
|
|
20212
|
-
import { spawnSync } from "node:child_process";
|
|
20682
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
20213
20683
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
20214
20684
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
20215
20685
|
var remoteProvider = (cwd) => {
|
|
20216
|
-
const r =
|
|
20686
|
+
const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
20217
20687
|
if (r.status !== 0)
|
|
20218
20688
|
return null;
|
|
20219
20689
|
const url = (r.stdout ?? "").trim();
|
|
@@ -20250,7 +20720,7 @@ var remoteEndpoint = (remote) => {
|
|
|
20250
20720
|
var configuredSshEndpoint = (host) => {
|
|
20251
20721
|
if (host.startsWith("-"))
|
|
20252
20722
|
return null;
|
|
20253
|
-
const resolved =
|
|
20723
|
+
const resolved = spawnSync2("ssh", ["-G", host], {
|
|
20254
20724
|
encoding: "utf8",
|
|
20255
20725
|
timeout: 5000,
|
|
20256
20726
|
maxBuffer: 1024 * 1024,
|
|
@@ -20300,7 +20770,7 @@ var readVcsConfig = () => {
|
|
|
20300
20770
|
const file = vcsConfigPath();
|
|
20301
20771
|
let raw;
|
|
20302
20772
|
try {
|
|
20303
|
-
raw =
|
|
20773
|
+
raw = fs6.readFileSync(file, "utf8");
|
|
20304
20774
|
} catch {
|
|
20305
20775
|
return { status: "missing", path: file, config: {} };
|
|
20306
20776
|
}
|
|
@@ -20401,7 +20871,7 @@ function vcsCliIdentity(cwd) {
|
|
|
20401
20871
|
PATH: process.env.PATH ?? "",
|
|
20402
20872
|
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
20403
20873
|
};
|
|
20404
|
-
const result =
|
|
20874
|
+
const result = spawnSync2(bin, ["api", "user"], {
|
|
20405
20875
|
cwd: root,
|
|
20406
20876
|
encoding: "utf8",
|
|
20407
20877
|
env
|
|
@@ -20443,7 +20913,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20443
20913
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
20444
20914
|
descriptionPreview: desc.slice(0, 600)
|
|
20445
20915
|
});
|
|
20446
|
-
const remoteResult =
|
|
20916
|
+
const remoteResult = spawnSync2("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
20447
20917
|
cwd,
|
|
20448
20918
|
encoding: "utf8"
|
|
20449
20919
|
});
|
|
@@ -20459,7 +20929,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20459
20929
|
if (!hostingApiHostMatches(remote, host))
|
|
20460
20930
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
20461
20931
|
if (provider === "gitlab") {
|
|
20462
|
-
const run = (args) =>
|
|
20932
|
+
const run = (args) => spawnSync2("glab", ["api", ...args], {
|
|
20463
20933
|
cwd,
|
|
20464
20934
|
encoding: "utf8",
|
|
20465
20935
|
env
|
|
@@ -20479,7 +20949,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20479
20949
|
});
|
|
20480
20950
|
}
|
|
20481
20951
|
} else if (provider === "github") {
|
|
20482
|
-
const r =
|
|
20952
|
+
const r = spawnSync2("gh", [
|
|
20483
20953
|
"pr",
|
|
20484
20954
|
"list",
|
|
20485
20955
|
"--repo",
|
|
@@ -20538,66 +21008,6 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
20538
21008
|
return { ok: true };
|
|
20539
21009
|
};
|
|
20540
21010
|
|
|
20541
|
-
// packages/workit-core/src/core/workers.ts
|
|
20542
|
-
var same = (left, right) => {
|
|
20543
|
-
try {
|
|
20544
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
20545
|
-
} catch {
|
|
20546
|
-
return false;
|
|
20547
|
-
}
|
|
20548
|
-
};
|
|
20549
|
-
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
20550
|
-
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
20551
|
-
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
20552
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
20553
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
20554
|
-
const code = char.charCodeAt(0);
|
|
20555
|
-
return code < 32 || code === 127;
|
|
20556
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
20557
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
20558
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
20559
|
-
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
20560
|
-
const owner = workspace.writer?.owner;
|
|
20561
|
-
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
20562
|
-
};
|
|
20563
|
-
function assertProductWriteAllowed(input) {
|
|
20564
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
20565
|
-
return failure2("invalid_input", "invalid product write path");
|
|
20566
|
-
if (!input.store)
|
|
20567
|
-
return failure2("permission_denied", "write authorization requires core state");
|
|
20568
|
-
const task = input.store.readTask(input.task.id);
|
|
20569
|
-
if (!task.ok)
|
|
20570
|
-
return task;
|
|
20571
|
-
const workspace = input.store.readWorkspace();
|
|
20572
|
-
if (!workspace.ok)
|
|
20573
|
-
return workspace;
|
|
20574
|
-
if (!workspace.data)
|
|
20575
|
-
return failure2("not_found", "workspace not found");
|
|
20576
|
-
const workspaceRecord = workspace.data;
|
|
20577
|
-
if (task.data.status !== "active")
|
|
20578
|
-
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
20579
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
20580
|
-
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
20581
|
-
const writer = workspaceRecord.writer;
|
|
20582
|
-
if (!writer)
|
|
20583
|
-
return failure2("permission_denied", "checkout has no writer owner");
|
|
20584
|
-
if (writer.state === "uncertain")
|
|
20585
|
-
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
20586
|
-
owner: writer.owner
|
|
20587
|
-
});
|
|
20588
|
-
const caller = callerValue(input.caller);
|
|
20589
|
-
const workerId = workerIdOf(input.caller);
|
|
20590
|
-
const owner = writer.owner;
|
|
20591
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
20592
|
-
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
20593
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
20594
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
20595
|
-
return failure2("permission_denied", "only implementers can own product writes");
|
|
20596
|
-
if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
|
|
20597
|
-
return failure2("recovery_required", "worker session is not an active writer");
|
|
20598
|
-
return success(null, workspaceRecord.revision, owner);
|
|
20599
|
-
}
|
|
20600
|
-
|
|
20601
21011
|
// packages/workit-core/src/core/auto-approval.ts
|
|
20602
21012
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
20603
21013
|
var OPERATION_CLASS = {
|
|
@@ -20884,7 +21294,7 @@ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
|
20884
21294
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
20885
21295
|
let root;
|
|
20886
21296
|
try {
|
|
20887
|
-
root =
|
|
21297
|
+
root = fs7.realpathSync(checkoutRoot);
|
|
20888
21298
|
} catch {
|
|
20889
21299
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
20890
21300
|
}
|
|
@@ -20907,8 +21317,8 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
20907
21317
|
if (!segment)
|
|
20908
21318
|
continue;
|
|
20909
21319
|
current = path9.join(current, segment);
|
|
20910
|
-
const stat =
|
|
20911
|
-
const real =
|
|
21320
|
+
const stat = fs7.lstatSync(current);
|
|
21321
|
+
const real = fs7.realpathSync(current);
|
|
20912
21322
|
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
20913
21323
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
20914
21324
|
fields: [
|
|
@@ -20925,7 +21335,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
20925
21335
|
if (current !== target && !stat.isDirectory())
|
|
20926
21336
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
20927
21337
|
}
|
|
20928
|
-
if (digestBytes2(
|
|
21338
|
+
if (digestBytes2(fs7.readFileSync(target)) !== reference.digest)
|
|
20929
21339
|
return failure2("permission_denied", "approved document bytes have changed");
|
|
20930
21340
|
} catch {
|
|
20931
21341
|
return failure2("invalid_input", "approved document is unavailable");
|
|
@@ -21288,18 +21698,41 @@ function reconcileAction(input) {
|
|
|
21288
21698
|
var bindingCovers = scopeCovers;
|
|
21289
21699
|
var verifyDecisionContent = verifyContentRefs;
|
|
21290
21700
|
|
|
21291
|
-
// packages/workit-core/src/core/
|
|
21292
|
-
var
|
|
21293
|
-
|
|
21294
|
-
|
|
21295
|
-
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21701
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
21702
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
21703
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
21704
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
21705
|
+
var canonicalPath = (value) => {
|
|
21706
|
+
if (typeof value !== "string" || !value)
|
|
21707
|
+
return null;
|
|
21708
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
21709
|
+
return null;
|
|
21710
|
+
const normalized = path10.posix.normalize(value);
|
|
21711
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
21712
|
+
return null;
|
|
21713
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
21714
|
+
};
|
|
21715
|
+
var canonicalScope = (scope) => {
|
|
21716
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
21717
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
21718
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
21719
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
21720
|
+
return null;
|
|
21721
|
+
return {
|
|
21722
|
+
description: scope.description,
|
|
21723
|
+
paths: [...new Set(normalizedPaths)],
|
|
21724
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
21725
|
+
};
|
|
21726
|
+
};
|
|
21727
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
21728
|
+
var scopeMatches = (value, scope) => {
|
|
21729
|
+
const normalized = canonicalScope(scope);
|
|
21730
|
+
if (!normalized)
|
|
21731
|
+
return false;
|
|
21732
|
+
const paths = normalized.paths;
|
|
21733
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
21734
|
+
};
|
|
21735
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
21303
21736
|
var compareCodeUnits2 = (left, right) => {
|
|
21304
21737
|
const length = Math.min(left.length, right.length);
|
|
21305
21738
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -21309,362 +21742,8 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
21309
21742
|
}
|
|
21310
21743
|
return left.length - right.length;
|
|
21311
21744
|
};
|
|
21312
|
-
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits2);
|
|
21313
|
-
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
|
|
21314
|
-
var normalizedScope = (scope) => ({
|
|
21315
|
-
description: scope.description,
|
|
21316
|
-
paths: stableUnique(scope.paths),
|
|
21317
|
-
exclusions: stableUnique(scope.exclusions)
|
|
21318
|
-
});
|
|
21319
|
-
var normalizedRefList = (values) => stableList(values);
|
|
21320
|
-
var normalizeAssessment = (assessment) => ({
|
|
21321
|
-
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
21322
|
-
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
21323
|
-
name,
|
|
21324
|
-
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
21325
|
-
])),
|
|
21326
|
-
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
21327
|
-
...consequence,
|
|
21328
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
21329
|
-
}))),
|
|
21330
|
-
verification: stableList(assessment.verification.map((verification) => ({
|
|
21331
|
-
...verification,
|
|
21332
|
-
scope: normalizedScope(verification.scope),
|
|
21333
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
21334
|
-
gaps: stableUnique(verification.gaps)
|
|
21335
|
-
})))
|
|
21336
|
-
});
|
|
21337
|
-
var normalizeConstraint = (constraint) => ({
|
|
21338
|
-
...constraint,
|
|
21339
|
-
source: constraint.source,
|
|
21340
|
-
requires: stableList(constraint.requires.map((requirement) => ({
|
|
21341
|
-
...requirement,
|
|
21342
|
-
scope: normalizedScope(requirement.scope),
|
|
21343
|
-
refs: normalizedRefList(requirement.refs)
|
|
21344
|
-
})))
|
|
21345
|
-
});
|
|
21346
|
-
var normalizeDecision = (decision) => ({
|
|
21347
|
-
...decision,
|
|
21348
|
-
binding: {
|
|
21349
|
-
...decision.binding,
|
|
21350
|
-
scope: normalizedScope(decision.binding.scope),
|
|
21351
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
21352
|
-
},
|
|
21353
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
21354
|
-
});
|
|
21355
|
-
var normalizeIntent = (intent) => ({
|
|
21356
|
-
...intent,
|
|
21357
|
-
scope: normalizedScope(intent.scope),
|
|
21358
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
21359
|
-
});
|
|
21360
|
-
var normalizePrior = (prior) => ({
|
|
21361
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
21362
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
21363
|
-
...finding,
|
|
21364
|
-
scope: normalizedScope(finding.scope),
|
|
21365
|
-
refs: normalizedRefList(finding.refs),
|
|
21366
|
-
resolution: finding.resolution ? {
|
|
21367
|
-
...finding.resolution,
|
|
21368
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
21369
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
21370
|
-
} : null
|
|
21371
|
-
}))),
|
|
21372
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
21373
|
-
...requirement,
|
|
21374
|
-
scope: normalizedScope(requirement.scope)
|
|
21375
|
-
})))
|
|
21376
|
-
});
|
|
21377
|
-
var invalidFields = (error) => ({
|
|
21378
|
-
fields: error.issues.map((issue) => ({
|
|
21379
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
21380
|
-
reason: issue.message
|
|
21381
|
-
}))
|
|
21382
|
-
});
|
|
21383
|
-
function normalizeResolverInput(input) {
|
|
21384
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
21385
|
-
if (!parsed.success)
|
|
21386
|
-
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
21387
|
-
const value = parsed.data;
|
|
21388
|
-
const signals = value.assessment.signals;
|
|
21389
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
21390
|
-
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
21391
|
-
fields: [
|
|
21392
|
-
{
|
|
21393
|
-
path: "assessment.signals",
|
|
21394
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
21395
|
-
}
|
|
21396
|
-
]
|
|
21397
|
-
});
|
|
21398
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
21399
|
-
if (new Set(ids).size !== ids.length)
|
|
21400
|
-
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
21401
|
-
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
21402
|
-
});
|
|
21403
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
21404
|
-
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
21405
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
21406
|
-
});
|
|
21407
|
-
const normalized = {
|
|
21408
|
-
intent: normalizeIntent(value.intent),
|
|
21409
|
-
assessment: normalizeAssessment(value.assessment),
|
|
21410
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
21411
|
-
prior: normalizePrior(value.prior),
|
|
21412
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
21413
|
-
policyVersion: POLICY_VERSION
|
|
21414
|
-
};
|
|
21415
|
-
return success(null, null, normalized);
|
|
21416
|
-
}
|
|
21417
|
-
var requirement = (spec) => ({
|
|
21418
|
-
...spec,
|
|
21419
|
-
id: requirementId({
|
|
21420
|
-
ruleId: spec.ruleId,
|
|
21421
|
-
scope: spec.scope,
|
|
21422
|
-
satisfaction: spec.satisfaction,
|
|
21423
|
-
before: spec.before,
|
|
21424
|
-
dependentAction: spec.dependentAction
|
|
21425
|
-
})
|
|
21426
|
-
});
|
|
21427
|
-
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
21428
|
-
function resolveRequirements(input) {
|
|
21429
|
-
const { signals } = input.assessment;
|
|
21430
|
-
const scope = input.intent.scope;
|
|
21431
|
-
const requirements = [];
|
|
21432
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
21433
|
-
const hasUnknownFact = [
|
|
21434
|
-
...input.assessment.facts,
|
|
21435
|
-
...input.assessment.consequences.map(({ fact }) => fact)
|
|
21436
|
-
].some((fact) => fact.basis === "unknown");
|
|
21437
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
21438
|
-
requirements.push(requirement({
|
|
21439
|
-
ruleId: "consequential-unknown",
|
|
21440
|
-
dimension: "investigation",
|
|
21441
|
-
scope,
|
|
21442
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
21443
|
-
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
21444
|
-
...hasUnknownFact ? ["unknown fact"] : []
|
|
21445
|
-
].join(", ")}`,
|
|
21446
|
-
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
21447
|
-
before: "dependent_action",
|
|
21448
|
-
dependentAction: null,
|
|
21449
|
-
acceptanceAllowed: false
|
|
21450
|
-
}));
|
|
21451
|
-
if (signals.productChoiceOpen.value === true)
|
|
21452
|
-
requirements.push(requirement({
|
|
21453
|
-
ruleId: "product-decision",
|
|
21454
|
-
dimension: "decisions",
|
|
21455
|
-
scope,
|
|
21456
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
21457
|
-
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
21458
|
-
before: "dependent_action",
|
|
21459
|
-
dependentAction: null,
|
|
21460
|
-
acceptanceAllowed: false
|
|
21461
|
-
}));
|
|
21462
|
-
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
21463
|
-
if (signals.behaviorChange.value === true)
|
|
21464
|
-
requirements.push(requirement({
|
|
21465
|
-
ruleId: "behavioral-verification",
|
|
21466
|
-
dimension: "testing",
|
|
21467
|
-
scope,
|
|
21468
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
21469
|
-
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
21470
|
-
before: "close",
|
|
21471
|
-
dependentAction: null,
|
|
21472
|
-
acceptanceAllowed: false
|
|
21473
|
-
}), requirement({
|
|
21474
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
21475
|
-
dimension: "review",
|
|
21476
|
-
scope,
|
|
21477
|
-
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
21478
|
-
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
21479
|
-
before: "close",
|
|
21480
|
-
dependentAction: null,
|
|
21481
|
-
acceptanceAllowed: !independentBehaviorReview
|
|
21482
|
-
}));
|
|
21483
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
21484
|
-
requirements.push(requirement({
|
|
21485
|
-
ruleId: "mechanical-existing-checks",
|
|
21486
|
-
dimension: "verification",
|
|
21487
|
-
scope,
|
|
21488
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
21489
|
-
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
21490
|
-
before: "close",
|
|
21491
|
-
dependentAction: null,
|
|
21492
|
-
acceptanceAllowed: true
|
|
21493
|
-
}), requirement({
|
|
21494
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
21495
|
-
dimension: "review",
|
|
21496
|
-
scope,
|
|
21497
|
-
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
21498
|
-
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
21499
|
-
before: "close",
|
|
21500
|
-
dependentAction: null,
|
|
21501
|
-
acceptanceAllowed: !input.preferences?.thorough
|
|
21502
|
-
}));
|
|
21503
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
21504
|
-
requirements.push(requirement({
|
|
21505
|
-
ruleId: "pre-pr-cleanup",
|
|
21506
|
-
dimension: "verification",
|
|
21507
|
-
scope,
|
|
21508
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
21509
|
-
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
21510
|
-
before: "dependent_action",
|
|
21511
|
-
dependentAction: "hosting.pull_request",
|
|
21512
|
-
acceptanceAllowed: true
|
|
21513
|
-
}));
|
|
21514
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
21515
|
-
requirements.push(requirement({
|
|
21516
|
-
ruleId: "durable-spec",
|
|
21517
|
-
dimension: "artifacts",
|
|
21518
|
-
scope,
|
|
21519
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
21520
|
-
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
21521
|
-
before: "write",
|
|
21522
|
-
dependentAction: null,
|
|
21523
|
-
acceptanceAllowed: true
|
|
21524
|
-
}));
|
|
21525
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
21526
|
-
requirements.push(requirement({
|
|
21527
|
-
ruleId: "coordination-plan",
|
|
21528
|
-
dimension: "continuity",
|
|
21529
|
-
scope,
|
|
21530
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
21531
|
-
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
21532
|
-
before: "dependent_action",
|
|
21533
|
-
dependentAction: null,
|
|
21534
|
-
acceptanceAllowed: true
|
|
21535
|
-
}));
|
|
21536
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
21537
|
-
requirements.push(requirement({
|
|
21538
|
-
ruleId: "helper-usefulness",
|
|
21539
|
-
dimension: "delegation",
|
|
21540
|
-
scope,
|
|
21541
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
21542
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
21543
|
-
before: "dependent_action",
|
|
21544
|
-
dependentAction: null,
|
|
21545
|
-
acceptanceAllowed: true
|
|
21546
|
-
}));
|
|
21547
|
-
for (const constraint of input.constraints)
|
|
21548
|
-
for (const obligation of constraint.requires) {
|
|
21549
|
-
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
21550
|
-
requirements.push(requirement({
|
|
21551
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
21552
|
-
kind: obligation.kind,
|
|
21553
|
-
scope: normalizedScope(obligation.scope),
|
|
21554
|
-
refs: normalizedRefList(obligation.refs),
|
|
21555
|
-
method: obligation.method,
|
|
21556
|
-
before: obligation.before,
|
|
21557
|
-
dependentAction: obligation.dependentAction
|
|
21558
|
-
})}`,
|
|
21559
|
-
dimension,
|
|
21560
|
-
scope: obligation.scope,
|
|
21561
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
21562
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
21563
|
-
before: obligation.before,
|
|
21564
|
-
dependentAction: obligation.dependentAction,
|
|
21565
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
21566
|
-
}));
|
|
21567
|
-
}
|
|
21568
|
-
const seen = new Set;
|
|
21569
|
-
return requirements.filter((item) => {
|
|
21570
|
-
if (seen.has(item.id))
|
|
21571
|
-
return false;
|
|
21572
|
-
seen.add(item.id);
|
|
21573
|
-
return true;
|
|
21574
|
-
});
|
|
21575
|
-
}
|
|
21576
|
-
function resolvePolicy(input) {
|
|
21577
|
-
const normalized = normalizeResolverInput(input);
|
|
21578
|
-
if (!normalized.ok)
|
|
21579
|
-
return normalized;
|
|
21580
|
-
const requirements = resolveRequirements(normalized.data);
|
|
21581
|
-
const policy = {
|
|
21582
|
-
policyVersion: POLICY_VERSION,
|
|
21583
|
-
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
21584
|
-
requirements
|
|
21585
|
-
};
|
|
21586
|
-
const parsed = policySchema.safeParse(policy);
|
|
21587
|
-
if (!parsed.success)
|
|
21588
|
-
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
21589
|
-
return success(null, null, parsed.data);
|
|
21590
|
-
}
|
|
21591
|
-
function diffPolicy(previous, next, reason, now) {
|
|
21592
|
-
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
21593
|
-
throw new TypeError("previous policy is invalid");
|
|
21594
|
-
if (!policySchema.safeParse(next).success)
|
|
21595
|
-
throw new TypeError("next policy is invalid");
|
|
21596
|
-
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
21597
|
-
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
21598
|
-
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits2);
|
|
21599
|
-
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits2);
|
|
21600
|
-
if (added.length === 0 && retired.length === 0)
|
|
21601
|
-
return null;
|
|
21602
|
-
const normalizedReason = reason.trim();
|
|
21603
|
-
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
21604
|
-
const change = {
|
|
21605
|
-
recordedAt: now,
|
|
21606
|
-
fromInputDigest: previous?.inputDigest ?? null,
|
|
21607
|
-
toInputDigest: next.inputDigest,
|
|
21608
|
-
added,
|
|
21609
|
-
retired,
|
|
21610
|
-
reason: changeReason
|
|
21611
|
-
};
|
|
21612
|
-
const parsed = policyChangeSchema.safeParse(change);
|
|
21613
|
-
if (!parsed.success)
|
|
21614
|
-
throw new TypeError("policy change is invalid");
|
|
21615
|
-
return parsed.data;
|
|
21616
|
-
}
|
|
21617
|
-
|
|
21618
|
-
// packages/workit-core/src/core/task-evaluation.ts
|
|
21619
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
21620
|
-
import * as fs7 from "node:fs";
|
|
21621
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
21622
|
-
import path10 from "node:path";
|
|
21623
|
-
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
21624
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
21625
|
-
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
21626
|
-
var canonicalPath = (value) => {
|
|
21627
|
-
if (typeof value !== "string" || !value)
|
|
21628
|
-
return null;
|
|
21629
|
-
if (value.includes("\\") || value.split("/").includes(".."))
|
|
21630
|
-
return null;
|
|
21631
|
-
const normalized = path10.posix.normalize(value);
|
|
21632
|
-
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
21633
|
-
return null;
|
|
21634
|
-
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
21635
|
-
};
|
|
21636
|
-
var canonicalScope = (scope) => {
|
|
21637
|
-
const paths = scope.paths.length ? scope.paths : ["."];
|
|
21638
|
-
const normalizedPaths = paths.map(canonicalPath);
|
|
21639
|
-
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
21640
|
-
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
21641
|
-
return null;
|
|
21642
|
-
return {
|
|
21643
|
-
description: scope.description,
|
|
21644
|
-
paths: [...new Set(normalizedPaths)],
|
|
21645
|
-
exclusions: [...new Set(normalizedExclusions)]
|
|
21646
|
-
};
|
|
21647
|
-
};
|
|
21648
|
-
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
21649
|
-
var scopeMatches = (value, scope) => {
|
|
21650
|
-
const normalized = canonicalScope(scope);
|
|
21651
|
-
if (!normalized)
|
|
21652
|
-
return false;
|
|
21653
|
-
const paths = normalized.paths;
|
|
21654
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
21655
|
-
};
|
|
21656
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
21657
|
-
var compareCodeUnits3 = (left, right) => {
|
|
21658
|
-
const length = Math.min(left.length, right.length);
|
|
21659
|
-
for (let index = 0;index < length; index += 1) {
|
|
21660
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
21661
|
-
if (difference !== 0)
|
|
21662
|
-
return difference;
|
|
21663
|
-
}
|
|
21664
|
-
return left.length - right.length;
|
|
21665
|
-
};
|
|
21666
21745
|
var gitPaths = (root) => {
|
|
21667
|
-
const result =
|
|
21746
|
+
const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
21668
21747
|
cwd: root,
|
|
21669
21748
|
encoding: "buffer"
|
|
21670
21749
|
});
|
|
@@ -21677,7 +21756,7 @@ var gitPaths = (root) => {
|
|
|
21677
21756
|
};
|
|
21678
21757
|
}
|
|
21679
21758
|
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
21680
|
-
const stagedDeleted =
|
|
21759
|
+
const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
21681
21760
|
if (stagedDeleted.status !== 0) {
|
|
21682
21761
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
21683
21762
|
if (!/not a git repository/i.test(stderr))
|
|
@@ -21690,7 +21769,7 @@ var gitPaths = (root) => {
|
|
|
21690
21769
|
var walk = (root, directory, output) => {
|
|
21691
21770
|
let names;
|
|
21692
21771
|
try {
|
|
21693
|
-
names =
|
|
21772
|
+
names = fs8.readdirSync(directory);
|
|
21694
21773
|
} catch {
|
|
21695
21774
|
return true;
|
|
21696
21775
|
}
|
|
@@ -21701,7 +21780,7 @@ var walk = (root, directory, output) => {
|
|
|
21701
21780
|
const target = path10.join(directory, name);
|
|
21702
21781
|
let stat;
|
|
21703
21782
|
try {
|
|
21704
|
-
stat =
|
|
21783
|
+
stat = fs8.lstatSync(target);
|
|
21705
21784
|
} catch {
|
|
21706
21785
|
uncertain = true;
|
|
21707
21786
|
continue;
|
|
@@ -21720,10 +21799,10 @@ var scopeRoots = (root, scope) => {
|
|
|
21720
21799
|
if (!inside(root, target))
|
|
21721
21800
|
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` }] });
|
|
21722
21801
|
let ancestor = target;
|
|
21723
|
-
while (!
|
|
21802
|
+
while (!fs8.existsSync(ancestor) && ancestor !== root)
|
|
21724
21803
|
ancestor = path10.dirname(ancestor);
|
|
21725
21804
|
try {
|
|
21726
|
-
if (!inside(root,
|
|
21805
|
+
if (!inside(root, fs8.realpathSync(ancestor)))
|
|
21727
21806
|
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` }] });
|
|
21728
21807
|
} catch {
|
|
21729
21808
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -21735,7 +21814,7 @@ var scopeRoots = (root, scope) => {
|
|
|
21735
21814
|
function captureCandidate(root, scope, environment = []) {
|
|
21736
21815
|
let checkout;
|
|
21737
21816
|
try {
|
|
21738
|
-
checkout =
|
|
21817
|
+
checkout = fs8.realpathSync(root);
|
|
21739
21818
|
} catch {
|
|
21740
21819
|
return failure2("invalid_input", "candidate root does not exist");
|
|
21741
21820
|
}
|
|
@@ -21753,11 +21832,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21753
21832
|
if (item === ".git" || item === ".workit")
|
|
21754
21833
|
continue;
|
|
21755
21834
|
names.add(item);
|
|
21756
|
-
if (!
|
|
21835
|
+
if (!fs8.existsSync(target))
|
|
21757
21836
|
continue;
|
|
21758
21837
|
let stat;
|
|
21759
21838
|
try {
|
|
21760
|
-
stat =
|
|
21839
|
+
stat = fs8.lstatSync(target);
|
|
21761
21840
|
} catch {
|
|
21762
21841
|
uncertain = true;
|
|
21763
21842
|
continue;
|
|
@@ -21775,15 +21854,15 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21775
21854
|
continue;
|
|
21776
21855
|
const target = path10.join(checkout, item);
|
|
21777
21856
|
try {
|
|
21778
|
-
const stat =
|
|
21857
|
+
const stat = fs8.lstatSync(target);
|
|
21779
21858
|
if (stat.isSymbolicLink()) {
|
|
21780
|
-
const link =
|
|
21859
|
+
const link = fs8.readlinkSync(target);
|
|
21781
21860
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
21782
21861
|
} else if (stat.isFile()) {
|
|
21783
21862
|
files.push({
|
|
21784
21863
|
path: item,
|
|
21785
21864
|
kind: "file",
|
|
21786
|
-
digest: digestBytes3(
|
|
21865
|
+
digest: digestBytes3(fs8.readFileSync(target)),
|
|
21787
21866
|
executable: (stat.mode & 73) !== 0
|
|
21788
21867
|
});
|
|
21789
21868
|
} else if (!stat.isDirectory()) {
|
|
@@ -21805,10 +21884,10 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21805
21884
|
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
21806
21885
|
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
21807
21886
|
return failure2("invalid_input", "candidate environment names must be unique and non-empty");
|
|
21808
|
-
const values = supplied.toSorted((left, right) =>
|
|
21887
|
+
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
21809
21888
|
if (values.some(({ value }) => value === null))
|
|
21810
21889
|
completeness = "uncertain";
|
|
21811
|
-
const headResult =
|
|
21890
|
+
const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
21812
21891
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
21813
21892
|
completeness = "uncertain";
|
|
21814
21893
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -21859,420 +21938,862 @@ function evaluateEvidence(task, candidate) {
|
|
|
21859
21938
|
if (evidence.kind === "check" || evidence.kind === "review") {
|
|
21860
21939
|
if (!evidence.beforeCandidateId || !evidence.candidateId)
|
|
21861
21940
|
return {
|
|
21862
|
-
evidenceId: entry.id,
|
|
21863
|
-
status: "stale",
|
|
21864
|
-
reason: "check or review evidence lacks start and end candidates"
|
|
21941
|
+
evidenceId: entry.id,
|
|
21942
|
+
status: "stale",
|
|
21943
|
+
reason: "check or review evidence lacks start and end candidates"
|
|
21944
|
+
};
|
|
21945
|
+
if (evidence.beforeCandidateId !== evidence.candidateId)
|
|
21946
|
+
return {
|
|
21947
|
+
evidenceId: entry.id,
|
|
21948
|
+
status: "stale",
|
|
21949
|
+
reason: "candidate changed during the check or review"
|
|
21950
|
+
};
|
|
21951
|
+
}
|
|
21952
|
+
if (!evidence.candidateId || !current)
|
|
21953
|
+
return {
|
|
21954
|
+
evidenceId: entry.id,
|
|
21955
|
+
status: evidence.result,
|
|
21956
|
+
reason: "evidence has no candidate binding"
|
|
21957
|
+
};
|
|
21958
|
+
const observed = task.candidates.find((item) => item.id === evidence.candidateId) ?? (current.id === evidence.candidateId ? current : undefined);
|
|
21959
|
+
if (!observed || observed.completeness === "uncertain" || current.completeness === "uncertain")
|
|
21960
|
+
return { evidenceId: entry.id, status: "stale", reason: "candidate is missing or uncertain" };
|
|
21961
|
+
if (observed.id === current.id)
|
|
21962
|
+
return { evidenceId: entry.id, status: evidence.result, reason: "candidate is unchanged" };
|
|
21963
|
+
const scopes = evidenceScopes(task, evidence.requirementIds);
|
|
21964
|
+
const changed = changedPaths(observed, current);
|
|
21965
|
+
const environmentChanged = JSON.stringify(observed.environment) !== JSON.stringify(current.environment);
|
|
21966
|
+
if (!scopes.length || environmentChanged || changed.some((item) => scopes.some((scope) => pathRelevant(item, scope))))
|
|
21967
|
+
return { evidenceId: entry.id, status: "stale", reason: "relevant candidate state changed" };
|
|
21968
|
+
return {
|
|
21969
|
+
evidenceId: entry.id,
|
|
21970
|
+
status: evidence.result,
|
|
21971
|
+
reason: "candidate changed outside evidence scope"
|
|
21972
|
+
};
|
|
21973
|
+
});
|
|
21974
|
+
}
|
|
21975
|
+
var scopeCovers2 = scopeCovers;
|
|
21976
|
+
var checkPin = (assignment, beforeCandidateId, candidateId) => {
|
|
21977
|
+
const pin = assignment.candidateId ?? null;
|
|
21978
|
+
if (pin === null)
|
|
21979
|
+
return true;
|
|
21980
|
+
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
21981
|
+
};
|
|
21982
|
+
var resolveBinding = (evidence, pin, currentCandidateId) => {
|
|
21983
|
+
const bound = evidence.kind === "check" || evidence.kind === "review";
|
|
21984
|
+
const fallback = bound ? pin ?? currentCandidateId : null;
|
|
21985
|
+
return {
|
|
21986
|
+
beforeCandidateId: evidence.beforeCandidateId ?? fallback,
|
|
21987
|
+
candidateId: evidence.candidateId ?? fallback
|
|
21988
|
+
};
|
|
21989
|
+
};
|
|
21990
|
+
var findingVerificationPasses = (findingCandidateId, evidence) => evidence.status === "passed" && (evidence.kind === "check" || evidence.kind === "review") && ((findingCandidateId ?? null) === null || evidence.candidateId != null);
|
|
21991
|
+
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")));
|
|
21992
|
+
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 }));
|
|
21993
|
+
var evidenceMatchesRequirement = (kind, evidenceKind, dimension, ruleId) => {
|
|
21994
|
+
if (kind !== "passed")
|
|
21995
|
+
return false;
|
|
21996
|
+
if (dimension === "testing" || dimension === "verification")
|
|
21997
|
+
return evidenceKind === "check" || ruleId === "pre-pr-cleanup" && evidenceKind === "artifact";
|
|
21998
|
+
if (dimension === "review")
|
|
21999
|
+
return evidenceKind === "review";
|
|
22000
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
22001
|
+
return evidenceKind === "investigation";
|
|
22002
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
22003
|
+
return evidenceKind === "artifact";
|
|
22004
|
+
return false;
|
|
22005
|
+
};
|
|
22006
|
+
var expectedKinds = (dimension, ruleId) => {
|
|
22007
|
+
if (dimension === "testing")
|
|
22008
|
+
return "check";
|
|
22009
|
+
if (dimension === "verification")
|
|
22010
|
+
return ruleId === "pre-pr-cleanup" ? "check or artifact" : "check";
|
|
22011
|
+
if (dimension === "review")
|
|
22012
|
+
return "review";
|
|
22013
|
+
if (dimension === "investigation" || dimension === "challenge")
|
|
22014
|
+
return "investigation";
|
|
22015
|
+
if (dimension === "artifacts" || dimension === "continuity")
|
|
22016
|
+
return "artifact";
|
|
22017
|
+
if (dimension === "decisions")
|
|
22018
|
+
return "decision";
|
|
22019
|
+
return "worker report";
|
|
22020
|
+
};
|
|
22021
|
+
function evaluateRequirements(task, workspace, capabilities, candidate, checkoutRoot, _caller) {
|
|
22022
|
+
const evidence = evaluateEvidence(task, candidate);
|
|
22023
|
+
if (task.policy && task.policy.policyVersion !== POLICY_VERSION)
|
|
22024
|
+
return task.policy.requirements.map((requirement) => ({
|
|
22025
|
+
requirementId: requirement.id,
|
|
22026
|
+
status: "unsatisfied",
|
|
22027
|
+
evidenceIds: [],
|
|
22028
|
+
decisionIds: [],
|
|
22029
|
+
reason: "stored policy version is unsupported; reassessment is required"
|
|
22030
|
+
}));
|
|
22031
|
+
return (task.policy?.requirements ?? []).map((requirement) => {
|
|
22032
|
+
const related = task.evidence.map((entry, index) => ({ entry, evaluation: evidence[index] })).filter(({ entry }) => entry.data.requirementIds.includes(requirement.id));
|
|
22033
|
+
const passed = related.filter(({ entry, evaluation }) => {
|
|
22034
|
+
if (!evidenceMatchesRequirement(evaluation.status, entry.data.kind, requirement.dimension, requirement.ruleId))
|
|
22035
|
+
return false;
|
|
22036
|
+
if ((entry.data.kind === "check" || entry.data.kind === "review") && (!entry.data.beforeCandidateId || !entry.data.candidateId || entry.data.beforeCandidateId !== entry.data.candidateId))
|
|
22037
|
+
return false;
|
|
22038
|
+
if (requirement.dimension !== "review")
|
|
22039
|
+
return true;
|
|
22040
|
+
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
22041
|
+
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
22042
|
+
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));
|
|
22043
|
+
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
22044
|
+
});
|
|
22045
|
+
if (passed.length) {
|
|
22046
|
+
const currentId = candidate?.id ?? null;
|
|
22047
|
+
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;
|
|
22048
|
+
if (withRed.length)
|
|
22049
|
+
return {
|
|
22050
|
+
requirementId: requirement.id,
|
|
22051
|
+
status: "satisfied",
|
|
22052
|
+
evidenceIds: withRed.map(({ entry }) => entry.id),
|
|
22053
|
+
decisionIds: [],
|
|
22054
|
+
reason: "fresh applicable evidence passed"
|
|
21865
22055
|
};
|
|
21866
|
-
if (
|
|
22056
|
+
if (requirement.dimension === "testing")
|
|
21867
22057
|
return {
|
|
21868
|
-
|
|
21869
|
-
status: "
|
|
21870
|
-
|
|
22058
|
+
requirementId: requirement.id,
|
|
22059
|
+
status: "unsatisfied",
|
|
22060
|
+
evidenceIds: [],
|
|
22061
|
+
decisionIds: [],
|
|
22062
|
+
reason: "testing requirement has GREEN evidence but no preceding RED"
|
|
21871
22063
|
};
|
|
21872
22064
|
}
|
|
21873
|
-
if (
|
|
22065
|
+
if (requirement.dimension === "delegation") {
|
|
22066
|
+
const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
|
|
22067
|
+
const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
|
|
22068
|
+
if (reconciled)
|
|
22069
|
+
return {
|
|
22070
|
+
requirementId: requirement.id,
|
|
22071
|
+
status: "satisfied",
|
|
22072
|
+
evidenceIds: [],
|
|
22073
|
+
decisionIds: [],
|
|
22074
|
+
reason: "bounded helper completed and its result is recorded"
|
|
22075
|
+
};
|
|
22076
|
+
}
|
|
22077
|
+
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
22078
|
+
if (decisions.length)
|
|
21874
22079
|
return {
|
|
21875
|
-
|
|
21876
|
-
status:
|
|
21877
|
-
|
|
22080
|
+
requirementId: requirement.id,
|
|
22081
|
+
status: "satisfied",
|
|
22082
|
+
evidenceIds: [],
|
|
22083
|
+
decisionIds: decisions.map((decision) => decision.id),
|
|
22084
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
21878
22085
|
};
|
|
21879
|
-
const
|
|
21880
|
-
if (
|
|
21881
|
-
return {
|
|
21882
|
-
|
|
21883
|
-
|
|
21884
|
-
|
|
21885
|
-
|
|
21886
|
-
|
|
21887
|
-
|
|
21888
|
-
|
|
22086
|
+
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
22087
|
+
if (limitations.length)
|
|
22088
|
+
return {
|
|
22089
|
+
requirementId: requirement.id,
|
|
22090
|
+
status: "accepted_limitation",
|
|
22091
|
+
evidenceIds: [],
|
|
22092
|
+
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
22093
|
+
reason: "an applicable approved limitation permits the missing evidence"
|
|
22094
|
+
};
|
|
22095
|
+
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
22096
|
+
if (!unavailable && related.length > 0 && !passed.length)
|
|
22097
|
+
return {
|
|
22098
|
+
requirementId: requirement.id,
|
|
22099
|
+
status: "unsatisfied",
|
|
22100
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
22101
|
+
decisionIds: [],
|
|
22102
|
+
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
22103
|
+
};
|
|
22104
|
+
return {
|
|
22105
|
+
requirementId: requirement.id,
|
|
22106
|
+
status: unavailable ? "unavailable" : "unsatisfied",
|
|
22107
|
+
evidenceIds: related.map(({ entry }) => entry.id),
|
|
22108
|
+
decisionIds: [],
|
|
22109
|
+
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
22110
|
+
};
|
|
22111
|
+
});
|
|
22112
|
+
}
|
|
22113
|
+
function evaluateClosure(requestedOutcome, view) {
|
|
22114
|
+
if (requestedOutcome !== "stopped" && !view.task.policy)
|
|
22115
|
+
return failure2("requirements_unsatisfied", "task has not been assessed");
|
|
22116
|
+
if (view.task.policy && view.task.policy.policyVersion !== POLICY_VERSION)
|
|
22117
|
+
return failure2("requirements_unsatisfied", "stored policy version is unsupported; reassessment is required");
|
|
22118
|
+
const evaluations = view.requirements;
|
|
22119
|
+
const openFindings = view.task.findings.filter((entry) => entry.data.disposition === "open");
|
|
22120
|
+
if (requestedOutcome !== "stopped" && openFindings.length)
|
|
22121
|
+
return failure2("requirements_unsatisfied", "open findings must be resolved before closure");
|
|
22122
|
+
if (requestedOutcome !== "stopped") {
|
|
22123
|
+
const evidenceById = new Map(view.evidence.map((entry) => [entry.evidenceId, entry.status]));
|
|
22124
|
+
for (const entry of view.task.findings) {
|
|
22125
|
+
if (entry.data.disposition === "fixed") {
|
|
22126
|
+
const verified = entry.data.resolution?.evidenceIds.some((id) => {
|
|
22127
|
+
const evidence = view.task.evidence.find((item) => item.id === id);
|
|
22128
|
+
if (!evidence)
|
|
22129
|
+
return false;
|
|
22130
|
+
return findingVerificationPasses(entry.data.candidateId, {
|
|
22131
|
+
kind: evidence.data.kind,
|
|
22132
|
+
candidateId: evidence.data.candidateId,
|
|
22133
|
+
status: evidenceById.get(id) ?? "missing"
|
|
22134
|
+
});
|
|
22135
|
+
});
|
|
22136
|
+
if (!verified)
|
|
22137
|
+
return failure2("requirements_unsatisfied", "fixed findings require current verification");
|
|
22138
|
+
}
|
|
22139
|
+
if (entry.data.disposition === "deferred") {
|
|
22140
|
+
const valid = entry.data.resolution?.decisionIds.some((id) => {
|
|
22141
|
+
const decisionEntry = view.task.decisions.find((item) => item.id === id);
|
|
22142
|
+
const decision = decisionEntry?.data;
|
|
22143
|
+
return Boolean(decision && decisionEntry?.provenance.kind !== "imported" && decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.digest === decisionDigest(decision) && verifyDecisionContentAtRoot(view.workspace.root, decision.binding).ok && decision.binding.taskId === view.task.id && decision.binding.workspaceId === view.workspace.id && scopeCovers2(decision.binding.scope, entry.data.scope) && decision.requirementIds.some((requirementId) => view.task.policy?.requirements.some((requirement) => requirement.id === requirementId && requirement.acceptanceAllowed)));
|
|
22144
|
+
});
|
|
22145
|
+
if (!valid)
|
|
22146
|
+
return failure2("requirements_unsatisfied", "deferred findings require an approved limitation decision that references an acceptanceAllowed requirement and covers the finding scope");
|
|
22147
|
+
}
|
|
22148
|
+
}
|
|
22149
|
+
}
|
|
22150
|
+
const blocking = evaluations.filter((item) => {
|
|
22151
|
+
if (item.status !== "unsatisfied" && item.status !== "unavailable")
|
|
22152
|
+
return false;
|
|
22153
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
22154
|
+
return !requirement || requirement.before === "close";
|
|
22155
|
+
});
|
|
22156
|
+
if (requestedOutcome !== "stopped" && blocking.length)
|
|
22157
|
+
return failure2("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
22158
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
22159
|
+
requirements: blocking.map((item) => {
|
|
22160
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
22161
|
+
return {
|
|
22162
|
+
requirementId: item.requirementId,
|
|
22163
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
22164
|
+
reason: item.reason,
|
|
22165
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
22166
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
22167
|
+
};
|
|
22168
|
+
})
|
|
22169
|
+
});
|
|
22170
|
+
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
22171
|
+
if (requestedOutcome === "verified" && accepted.length)
|
|
22172
|
+
return failure2("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
22173
|
+
requirementIds: accepted.map((item) => item.requirementId)
|
|
22174
|
+
});
|
|
22175
|
+
const evidenceIds = [
|
|
22176
|
+
...new Set([
|
|
22177
|
+
...evaluations.flatMap((item) => item.evidenceIds),
|
|
22178
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
22179
|
+
])
|
|
22180
|
+
];
|
|
22181
|
+
const decisionIds = [
|
|
22182
|
+
...new Set([
|
|
22183
|
+
...evaluations.flatMap((item) => item.decisionIds),
|
|
22184
|
+
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
22185
|
+
])
|
|
22186
|
+
];
|
|
22187
|
+
return success(null, null, {
|
|
22188
|
+
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
22189
|
+
evidenceIds,
|
|
22190
|
+
decisionIds,
|
|
22191
|
+
requirementIds: evaluations.map((item) => item.requirementId)
|
|
22192
|
+
});
|
|
22193
|
+
}
|
|
22194
|
+
|
|
22195
|
+
// packages/workit-core/src/core/methods.ts
|
|
22196
|
+
var METHODS = {
|
|
22197
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
22198
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
22199
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
22200
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
22201
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
22202
|
+
"workit-debug": {},
|
|
22203
|
+
"workit-handoff": {},
|
|
22204
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
22205
|
+
};
|
|
22206
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
22207
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
22208
|
+
const keys = new Set([
|
|
22209
|
+
id,
|
|
22210
|
+
...definition.dimensions ?? [],
|
|
22211
|
+
...definition.ruleIds ?? []
|
|
22212
|
+
]);
|
|
22213
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
22214
|
+
if (id === "workit-review" && matches.length === 0)
|
|
22215
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
22216
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
22217
|
+
return {
|
|
22218
|
+
assurance: "unavailable",
|
|
22219
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
22220
|
+
};
|
|
22221
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
22222
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
22223
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
22224
|
+
};
|
|
22225
|
+
function selectMethods(policy, capabilities) {
|
|
22226
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
22227
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
22228
|
+
if (requirements.length === 0)
|
|
22229
|
+
return [];
|
|
22230
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
22231
|
+
const reasons = [
|
|
22232
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
22233
|
+
...capability.reasons
|
|
22234
|
+
];
|
|
22235
|
+
if (capability.assurance === "unavailable")
|
|
22236
|
+
reasons.unshift("required capability is unavailable");
|
|
22237
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
22238
|
+
});
|
|
22239
|
+
}
|
|
22240
|
+
|
|
22241
|
+
// packages/workit-core/src/core/task-context.ts
|
|
22242
|
+
function reconcileResume(view, observations = []) {
|
|
22243
|
+
if (observations.length > 0)
|
|
22244
|
+
return failure2("permission_denied", "worker observations require native host verification");
|
|
22245
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
22246
|
+
if (!candidate.ok)
|
|
22247
|
+
return candidate;
|
|
22248
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
22249
|
+
const blockers = [...view.task.progress.blockers];
|
|
22250
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
22251
|
+
blockers.push({
|
|
22252
|
+
reason: "worker state requires reconciliation",
|
|
22253
|
+
dependentAction: "resume",
|
|
22254
|
+
refs: []
|
|
22255
|
+
});
|
|
22256
|
+
return success(null, null, {
|
|
22257
|
+
candidate: candidate.data,
|
|
22258
|
+
staleEvidenceIds,
|
|
22259
|
+
workerUpdates: [],
|
|
22260
|
+
blockers,
|
|
22261
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
22262
|
+
});
|
|
22263
|
+
}
|
|
22264
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
22265
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
22266
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
22267
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
22268
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
22269
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
22270
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
22271
|
+
const separator = match.search(/[:=]/u);
|
|
22272
|
+
if (separator >= 0)
|
|
22273
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
22274
|
+
const space = match.search(/\s/u);
|
|
22275
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
22276
|
+
}).slice(0, limit);
|
|
22277
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
22278
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
22279
|
+
var compactDecisionChoice = (entry) => {
|
|
22280
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
22281
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
22282
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
22283
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
22284
|
+
};
|
|
22285
|
+
var compactReference = (ref) => {
|
|
22286
|
+
if (ref.kind === "file")
|
|
21889
22287
|
return {
|
|
21890
|
-
|
|
21891
|
-
|
|
21892
|
-
reason: "candidate changed outside evidence scope"
|
|
22288
|
+
kind: "file",
|
|
22289
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
21893
22290
|
};
|
|
21894
|
-
|
|
21895
|
-
|
|
21896
|
-
|
|
21897
|
-
|
|
21898
|
-
|
|
21899
|
-
|
|
21900
|
-
|
|
21901
|
-
return (beforeCandidateId ?? null) === pin || (candidateId ?? null) === pin;
|
|
22291
|
+
if (ref.kind === "record")
|
|
22292
|
+
return {
|
|
22293
|
+
kind: "record",
|
|
22294
|
+
collection: ref.collection,
|
|
22295
|
+
id: ref.id
|
|
22296
|
+
};
|
|
22297
|
+
return { kind: ref.kind };
|
|
21902
22298
|
};
|
|
21903
|
-
|
|
21904
|
-
const
|
|
21905
|
-
|
|
21906
|
-
|
|
21907
|
-
|
|
21908
|
-
|
|
22299
|
+
function compactTaskContext(view) {
|
|
22300
|
+
const decisions = view.task.decisions.slice().toSorted((left, right) => {
|
|
22301
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
22302
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
22303
|
+
if (leftAt !== rightAt)
|
|
22304
|
+
return leftAt > rightAt ? -1 : 1;
|
|
22305
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
22306
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
22307
|
+
id: entry.id,
|
|
22308
|
+
purpose: entry.data.purpose,
|
|
22309
|
+
status: entry.data.response,
|
|
22310
|
+
digest: entry.data.digest,
|
|
22311
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
22312
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
22313
|
+
}));
|
|
22314
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
22315
|
+
id: method.id,
|
|
22316
|
+
assurance: method.assurance,
|
|
22317
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
22318
|
+
})) : [];
|
|
22319
|
+
const gaps = Array.from(new Set([
|
|
22320
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
22321
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
22322
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
22323
|
+
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
22324
|
+
const context = {
|
|
22325
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
22326
|
+
status: view.task.status,
|
|
22327
|
+
decisions,
|
|
22328
|
+
methods,
|
|
22329
|
+
gaps,
|
|
22330
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
21909
22331
|
};
|
|
22332
|
+
let encoded = JSON.stringify(context);
|
|
22333
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
22334
|
+
if (context.gaps.length)
|
|
22335
|
+
context.gaps.pop();
|
|
22336
|
+
else if (context.decisions.length)
|
|
22337
|
+
context.decisions.pop();
|
|
22338
|
+
else
|
|
22339
|
+
context.methods.pop();
|
|
22340
|
+
encoded = JSON.stringify(context);
|
|
22341
|
+
}
|
|
22342
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
22343
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
22344
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
22345
|
+
encoded = JSON.stringify(context);
|
|
22346
|
+
}
|
|
22347
|
+
return encoded;
|
|
22348
|
+
}
|
|
22349
|
+
var exportDigest = (bundle) => sha256({
|
|
22350
|
+
schemaVersion: bundle.schemaVersion,
|
|
22351
|
+
exportedAt: bundle.exportedAt,
|
|
22352
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
22353
|
+
task: bundle.task
|
|
22354
|
+
});
|
|
22355
|
+
// packages/workit-core/src/core/external-action.ts
|
|
22356
|
+
var chainStepSchema = union([
|
|
22357
|
+
string2().min(1),
|
|
22358
|
+
object2({ branch: string2().min(1) }).strict(),
|
|
22359
|
+
object2({ pr: literal(true) }).strict()
|
|
22360
|
+
]);
|
|
22361
|
+
var externalActionSchema = discriminatedUnion("operation", [
|
|
22362
|
+
object2({
|
|
22363
|
+
operation: literal("git.branch_setup"),
|
|
22364
|
+
payload: object2({
|
|
22365
|
+
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
22366
|
+
sdd_dir: string2().optional(),
|
|
22367
|
+
target_branch: string2().optional(),
|
|
22368
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
22369
|
+
cwd: string2().min(1).optional()
|
|
22370
|
+
}).strict()
|
|
22371
|
+
}).strict(),
|
|
22372
|
+
object2({
|
|
22373
|
+
operation: literal("git.commit"),
|
|
22374
|
+
payload: object2({
|
|
22375
|
+
message: string2().min(1).optional(),
|
|
22376
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
22377
|
+
plan_branch: string2().min(1).optional(),
|
|
22378
|
+
cwd: string2().min(1).optional()
|
|
22379
|
+
}).strict()
|
|
22380
|
+
}).strict(),
|
|
22381
|
+
object2({
|
|
22382
|
+
operation: literal("git.push"),
|
|
22383
|
+
payload: object2({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
22384
|
+
}).strict(),
|
|
22385
|
+
object2({
|
|
22386
|
+
operation: literal("hosting.pull_request"),
|
|
22387
|
+
payload: object2({
|
|
22388
|
+
title: string2().min(1),
|
|
22389
|
+
body: string2().optional(),
|
|
22390
|
+
draft: boolean2().optional(),
|
|
22391
|
+
target_branch: string2().optional(),
|
|
22392
|
+
babysit: boolean2().optional(),
|
|
22393
|
+
cwd: string2().min(1).optional()
|
|
22394
|
+
}).strict()
|
|
22395
|
+
}).strict(),
|
|
22396
|
+
object2({
|
|
22397
|
+
operation: literal("hosting.merge"),
|
|
22398
|
+
payload: object2({
|
|
22399
|
+
target_branch: string2().min(1).optional(),
|
|
22400
|
+
source_branch: string2().min(1).optional(),
|
|
22401
|
+
cwd: string2().min(1).optional()
|
|
22402
|
+
}).strict()
|
|
22403
|
+
}).strict(),
|
|
22404
|
+
object2({
|
|
22405
|
+
operation: literal("hosting.delete_branch"),
|
|
22406
|
+
payload: object2({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
22407
|
+
}).strict(),
|
|
22408
|
+
object2({
|
|
22409
|
+
operation: literal("youtrack.update"),
|
|
22410
|
+
payload: object2({
|
|
22411
|
+
issueId: string2().min(1),
|
|
22412
|
+
markdown: string2().min(1),
|
|
22413
|
+
minutes: number2().positive().optional()
|
|
22414
|
+
}).strict()
|
|
22415
|
+
}).strict(),
|
|
22416
|
+
object2({
|
|
22417
|
+
operation: literal("youtrack.time"),
|
|
22418
|
+
payload: object2({
|
|
22419
|
+
issueId: string2().min(1),
|
|
22420
|
+
minutes: number2().positive(),
|
|
22421
|
+
text: string2().optional(),
|
|
22422
|
+
dateMs: number2().finite().optional()
|
|
22423
|
+
}).strict()
|
|
22424
|
+
}).strict(),
|
|
22425
|
+
object2({
|
|
22426
|
+
operation: literal("youtrack.meeting"),
|
|
22427
|
+
payload: object2({
|
|
22428
|
+
issueId: string2().min(1),
|
|
22429
|
+
minutes: number2().positive(),
|
|
22430
|
+
text: string2().min(1)
|
|
22431
|
+
}).strict()
|
|
22432
|
+
}).strict(),
|
|
22433
|
+
object2({
|
|
22434
|
+
operation: literal("changelog.apply"),
|
|
22435
|
+
payload: object2({
|
|
22436
|
+
entries: union([
|
|
22437
|
+
record(string2(), array(string2())),
|
|
22438
|
+
array(object2({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
22439
|
+
]).optional(),
|
|
22440
|
+
path: string2().optional(),
|
|
22441
|
+
normalize_only: boolean2().optional()
|
|
22442
|
+
}).strict()
|
|
22443
|
+
}).strict(),
|
|
22444
|
+
object2({
|
|
22445
|
+
operation: literal("context.read"),
|
|
22446
|
+
payload: object2({
|
|
22447
|
+
kind: _enum([
|
|
22448
|
+
"git",
|
|
22449
|
+
"pr",
|
|
22450
|
+
"youtrack",
|
|
22451
|
+
"github_issue",
|
|
22452
|
+
"gitlab_issue",
|
|
22453
|
+
"changelog",
|
|
22454
|
+
"release",
|
|
22455
|
+
"affected"
|
|
22456
|
+
]),
|
|
22457
|
+
range: string2().optional(),
|
|
22458
|
+
issueId: string2().optional(),
|
|
22459
|
+
issueUrl: string2().optional(),
|
|
22460
|
+
issueRef: string2().optional(),
|
|
22461
|
+
mode: string2().optional(),
|
|
22462
|
+
specPath: string2().optional(),
|
|
22463
|
+
planPath: string2().optional(),
|
|
22464
|
+
cwd: string2().min(1).optional()
|
|
22465
|
+
}).strict()
|
|
22466
|
+
}).strict()
|
|
22467
|
+
]);
|
|
22468
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
22469
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22470
|
+
|
|
22471
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
22472
|
+
var resolverInputSchema = object2({
|
|
22473
|
+
intent: intentSchema,
|
|
22474
|
+
assessment: assessmentSchema,
|
|
22475
|
+
constraints: array(constraintSchema),
|
|
22476
|
+
prior: object2({
|
|
22477
|
+
decisions: array(decisionSchema),
|
|
22478
|
+
findings: array(findingSchema),
|
|
22479
|
+
requirements: array(requirementSchema)
|
|
22480
|
+
}).strict(),
|
|
22481
|
+
preferences: object2({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
22482
|
+
}).strict();
|
|
22483
|
+
var compareCodeUnits3 = (left, right) => {
|
|
22484
|
+
const length = Math.min(left.length, right.length);
|
|
22485
|
+
for (let index = 0;index < length; index += 1) {
|
|
22486
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
22487
|
+
if (difference !== 0)
|
|
22488
|
+
return difference;
|
|
22489
|
+
}
|
|
22490
|
+
return left.length - right.length;
|
|
21910
22491
|
};
|
|
21911
|
-
var
|
|
21912
|
-
var
|
|
21913
|
-
var
|
|
21914
|
-
|
|
21915
|
-
|
|
21916
|
-
|
|
21917
|
-
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
|
|
21926
|
-
|
|
21927
|
-
|
|
21928
|
-
|
|
21929
|
-
|
|
21930
|
-
|
|
21931
|
-
|
|
21932
|
-
|
|
21933
|
-
|
|
21934
|
-
|
|
21935
|
-
|
|
21936
|
-
|
|
21937
|
-
|
|
21938
|
-
|
|
21939
|
-
|
|
21940
|
-
|
|
21941
|
-
|
|
21942
|
-
|
|
21943
|
-
|
|
21944
|
-
|
|
21945
|
-
|
|
21946
|
-
|
|
21947
|
-
|
|
21948
|
-
|
|
21949
|
-
|
|
21950
|
-
|
|
21951
|
-
|
|
21952
|
-
|
|
21953
|
-
|
|
21954
|
-
|
|
21955
|
-
|
|
21956
|
-
|
|
21957
|
-
|
|
21958
|
-
|
|
21959
|
-
|
|
21960
|
-
|
|
21961
|
-
|
|
21962
|
-
|
|
21963
|
-
|
|
21964
|
-
|
|
22492
|
+
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
22493
|
+
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
22494
|
+
var normalizedScope = (scope) => ({
|
|
22495
|
+
description: scope.description,
|
|
22496
|
+
paths: stableUnique(scope.paths),
|
|
22497
|
+
exclusions: stableUnique(scope.exclusions)
|
|
22498
|
+
});
|
|
22499
|
+
var normalizedRefList = (values) => stableList(values);
|
|
22500
|
+
var normalizeAssessment = (assessment) => ({
|
|
22501
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
22502
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
22503
|
+
name,
|
|
22504
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
22505
|
+
])),
|
|
22506
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
22507
|
+
...consequence,
|
|
22508
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
22509
|
+
}))),
|
|
22510
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
22511
|
+
...verification,
|
|
22512
|
+
scope: normalizedScope(verification.scope),
|
|
22513
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
22514
|
+
gaps: stableUnique(verification.gaps)
|
|
22515
|
+
})))
|
|
22516
|
+
});
|
|
22517
|
+
var normalizeConstraint = (constraint) => ({
|
|
22518
|
+
...constraint,
|
|
22519
|
+
source: constraint.source,
|
|
22520
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
22521
|
+
...requirement,
|
|
22522
|
+
scope: normalizedScope(requirement.scope),
|
|
22523
|
+
refs: normalizedRefList(requirement.refs)
|
|
22524
|
+
})))
|
|
22525
|
+
});
|
|
22526
|
+
var normalizeDecision = (decision) => ({
|
|
22527
|
+
...decision,
|
|
22528
|
+
binding: {
|
|
22529
|
+
...decision.binding,
|
|
22530
|
+
scope: normalizedScope(decision.binding.scope),
|
|
22531
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
22532
|
+
},
|
|
22533
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
22534
|
+
});
|
|
22535
|
+
var normalizeIntent = (intent) => ({
|
|
22536
|
+
...intent,
|
|
22537
|
+
scope: normalizedScope(intent.scope),
|
|
22538
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
22539
|
+
});
|
|
22540
|
+
var normalizePrior = (prior) => ({
|
|
22541
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
22542
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
22543
|
+
...finding,
|
|
22544
|
+
scope: normalizedScope(finding.scope),
|
|
22545
|
+
refs: normalizedRefList(finding.refs),
|
|
22546
|
+
resolution: finding.resolution ? {
|
|
22547
|
+
...finding.resolution,
|
|
22548
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
22549
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
22550
|
+
} : null
|
|
22551
|
+
}))),
|
|
22552
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
22553
|
+
...requirement,
|
|
22554
|
+
scope: normalizedScope(requirement.scope)
|
|
22555
|
+
})))
|
|
22556
|
+
});
|
|
22557
|
+
var invalidFields = (error) => ({
|
|
22558
|
+
fields: error.issues.map((issue) => ({
|
|
22559
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
22560
|
+
reason: issue.message
|
|
22561
|
+
}))
|
|
22562
|
+
});
|
|
22563
|
+
function normalizeResolverInput(input) {
|
|
22564
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
22565
|
+
if (!parsed.success)
|
|
22566
|
+
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
22567
|
+
const value = parsed.data;
|
|
22568
|
+
const signals = value.assessment.signals;
|
|
22569
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
22570
|
+
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
22571
|
+
fields: [
|
|
22572
|
+
{
|
|
22573
|
+
path: "assessment.signals",
|
|
22574
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
22575
|
+
}
|
|
22576
|
+
]
|
|
21965
22577
|
});
|
|
21966
|
-
|
|
21967
|
-
|
|
21968
|
-
|
|
21969
|
-
|
|
21970
|
-
|
|
21971
|
-
|
|
21972
|
-
|
|
21973
|
-
|
|
21974
|
-
|
|
21975
|
-
|
|
21976
|
-
|
|
21977
|
-
|
|
21978
|
-
|
|
21979
|
-
|
|
21980
|
-
|
|
21981
|
-
|
|
21982
|
-
|
|
21983
|
-
|
|
21984
|
-
};
|
|
21985
|
-
}
|
|
21986
|
-
if (requirement.dimension === "delegation") {
|
|
21987
|
-
const dot = (scope) => scope.paths.length > 0 ? scope : { ...scope, paths: ["."] };
|
|
21988
|
-
const reconciled = task.workers.some((worker) => worker.data.report?.outcome === "completed" && (worker.data.assignment.requirementIds.includes(requirement.id) || scopeCovers2(dot(requirement.scope), dot(worker.data.assignment.scope))));
|
|
21989
|
-
if (reconciled)
|
|
21990
|
-
return {
|
|
21991
|
-
requirementId: requirement.id,
|
|
21992
|
-
status: "satisfied",
|
|
21993
|
-
evidenceIds: [],
|
|
21994
|
-
decisionIds: [],
|
|
21995
|
-
reason: "bounded helper completed and its result is recorded"
|
|
21996
|
-
};
|
|
21997
|
-
}
|
|
21998
|
-
const decisions = requirement.dimension === "decisions" ? applicableRequirementDecision(task, workspace, requirement, checkoutRoot) : [];
|
|
21999
|
-
if (decisions.length)
|
|
22000
|
-
return {
|
|
22001
|
-
requirementId: requirement.id,
|
|
22002
|
-
status: "satisfied",
|
|
22003
|
-
evidenceIds: [],
|
|
22004
|
-
decisionIds: decisions.map((decision) => decision.id),
|
|
22005
|
-
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
22006
|
-
};
|
|
22007
|
-
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
22008
|
-
if (limitations.length)
|
|
22009
|
-
return {
|
|
22010
|
-
requirementId: requirement.id,
|
|
22011
|
-
status: "accepted_limitation",
|
|
22012
|
-
evidenceIds: [],
|
|
22013
|
-
decisionIds: limitations.map((decision) => task.decisions.find((entry) => entry.data === decision).id),
|
|
22014
|
-
reason: "an applicable approved limitation permits the missing evidence"
|
|
22015
|
-
};
|
|
22016
|
-
const unavailable = capabilities.some((capability) => capability.assurance === "unavailable" && (capability.name === requirement.dimension || capability.surface === requirement.dimension));
|
|
22017
|
-
if (!unavailable && related.length > 0 && !passed.length)
|
|
22018
|
-
return {
|
|
22019
|
-
requirementId: requirement.id,
|
|
22020
|
-
status: "unsatisfied",
|
|
22021
|
-
evidenceIds: related.map(({ entry }) => entry.id),
|
|
22022
|
-
decisionIds: [],
|
|
22023
|
-
reason: `no passing evidence (needs kind:${expectedKinds(requirement.dimension, requirement.ruleId)})`
|
|
22024
|
-
};
|
|
22025
|
-
return {
|
|
22026
|
-
requirementId: requirement.id,
|
|
22027
|
-
status: unavailable ? "unavailable" : "unsatisfied",
|
|
22028
|
-
evidenceIds: related.map(({ entry }) => entry.id),
|
|
22029
|
-
decisionIds: [],
|
|
22030
|
-
reason: unavailable ? "required capability is unavailable" : "no fresh passing evidence"
|
|
22031
|
-
};
|
|
22032
|
-
});
|
|
22578
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
22579
|
+
if (new Set(ids).size !== ids.length)
|
|
22580
|
+
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
22581
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
22582
|
+
});
|
|
22583
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
22584
|
+
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
22585
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
22586
|
+
});
|
|
22587
|
+
const normalized = {
|
|
22588
|
+
intent: normalizeIntent(value.intent),
|
|
22589
|
+
assessment: normalizeAssessment(value.assessment),
|
|
22590
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
22591
|
+
prior: normalizePrior(value.prior),
|
|
22592
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
22593
|
+
policyVersion: POLICY_VERSION
|
|
22594
|
+
};
|
|
22595
|
+
return success(null, null, normalized);
|
|
22033
22596
|
}
|
|
22034
|
-
|
|
22035
|
-
|
|
22036
|
-
|
|
22037
|
-
|
|
22038
|
-
|
|
22039
|
-
|
|
22040
|
-
|
|
22041
|
-
|
|
22042
|
-
|
|
22043
|
-
|
|
22044
|
-
|
|
22045
|
-
|
|
22046
|
-
|
|
22047
|
-
|
|
22048
|
-
|
|
22049
|
-
|
|
22050
|
-
|
|
22051
|
-
|
|
22052
|
-
|
|
22053
|
-
|
|
22054
|
-
|
|
22055
|
-
|
|
22056
|
-
|
|
22057
|
-
|
|
22058
|
-
|
|
22059
|
-
|
|
22060
|
-
|
|
22061
|
-
|
|
22062
|
-
|
|
22063
|
-
|
|
22064
|
-
|
|
22065
|
-
|
|
22066
|
-
|
|
22067
|
-
|
|
22068
|
-
|
|
22597
|
+
var requirement = (spec) => ({
|
|
22598
|
+
...spec,
|
|
22599
|
+
id: requirementId({
|
|
22600
|
+
ruleId: spec.ruleId,
|
|
22601
|
+
scope: spec.scope,
|
|
22602
|
+
satisfaction: spec.satisfaction,
|
|
22603
|
+
before: spec.before,
|
|
22604
|
+
dependentAction: spec.dependentAction
|
|
22605
|
+
})
|
|
22606
|
+
});
|
|
22607
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
22608
|
+
function resolveRequirements(input) {
|
|
22609
|
+
const { signals } = input.assessment;
|
|
22610
|
+
const scope = input.intent.scope;
|
|
22611
|
+
const requirements = [];
|
|
22612
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
22613
|
+
const hasUnknownFact = [
|
|
22614
|
+
...input.assessment.facts,
|
|
22615
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
22616
|
+
].some((fact) => fact.basis === "unknown");
|
|
22617
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
22618
|
+
requirements.push(requirement({
|
|
22619
|
+
ruleId: "consequential-unknown",
|
|
22620
|
+
dimension: "investigation",
|
|
22621
|
+
scope,
|
|
22622
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
22623
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
22624
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
22625
|
+
].join(", ")}`,
|
|
22626
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
22627
|
+
before: "dependent_action",
|
|
22628
|
+
dependentAction: null,
|
|
22629
|
+
acceptanceAllowed: false
|
|
22630
|
+
}));
|
|
22631
|
+
if (signals.productChoiceOpen.value === true)
|
|
22632
|
+
requirements.push(requirement({
|
|
22633
|
+
ruleId: "product-decision",
|
|
22634
|
+
dimension: "decisions",
|
|
22635
|
+
scope,
|
|
22636
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
22637
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
22638
|
+
before: "dependent_action",
|
|
22639
|
+
dependentAction: null,
|
|
22640
|
+
acceptanceAllowed: false
|
|
22641
|
+
}));
|
|
22642
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
22643
|
+
if (signals.behaviorChange.value === true)
|
|
22644
|
+
requirements.push(requirement({
|
|
22645
|
+
ruleId: "behavioral-verification",
|
|
22646
|
+
dimension: "testing",
|
|
22647
|
+
scope,
|
|
22648
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
22649
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
22650
|
+
before: "close",
|
|
22651
|
+
dependentAction: null,
|
|
22652
|
+
acceptanceAllowed: false
|
|
22653
|
+
}), requirement({
|
|
22654
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
22655
|
+
dimension: "review",
|
|
22656
|
+
scope,
|
|
22657
|
+
reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
|
|
22658
|
+
satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
22659
|
+
before: "close",
|
|
22660
|
+
dependentAction: null,
|
|
22661
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
22662
|
+
}));
|
|
22663
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
22664
|
+
requirements.push(requirement({
|
|
22665
|
+
ruleId: "mechanical-existing-checks",
|
|
22666
|
+
dimension: "verification",
|
|
22667
|
+
scope,
|
|
22668
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
22669
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
22670
|
+
before: "close",
|
|
22671
|
+
dependentAction: null,
|
|
22672
|
+
acceptanceAllowed: true
|
|
22673
|
+
}), requirement({
|
|
22674
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
22675
|
+
dimension: "review",
|
|
22676
|
+
scope,
|
|
22677
|
+
reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
|
|
22678
|
+
satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
|
|
22679
|
+
before: "close",
|
|
22680
|
+
dependentAction: null,
|
|
22681
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
22682
|
+
}));
|
|
22683
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
22684
|
+
requirements.push(requirement({
|
|
22685
|
+
ruleId: "pre-pr-cleanup",
|
|
22686
|
+
dimension: "verification",
|
|
22687
|
+
scope,
|
|
22688
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
22689
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
22690
|
+
before: "dependent_action",
|
|
22691
|
+
dependentAction: "hosting.pull_request",
|
|
22692
|
+
acceptanceAllowed: true
|
|
22693
|
+
}));
|
|
22694
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
22695
|
+
requirements.push(requirement({
|
|
22696
|
+
ruleId: "durable-spec",
|
|
22697
|
+
dimension: "artifacts",
|
|
22698
|
+
scope,
|
|
22699
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
22700
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
22701
|
+
before: "write",
|
|
22702
|
+
dependentAction: null,
|
|
22703
|
+
acceptanceAllowed: true
|
|
22704
|
+
}));
|
|
22705
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
22706
|
+
requirements.push(requirement({
|
|
22707
|
+
ruleId: "coordination-plan",
|
|
22708
|
+
dimension: "continuity",
|
|
22709
|
+
scope,
|
|
22710
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
22711
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
22712
|
+
before: "dependent_action",
|
|
22713
|
+
dependentAction: null,
|
|
22714
|
+
acceptanceAllowed: true
|
|
22715
|
+
}));
|
|
22716
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
22717
|
+
requirements.push(requirement({
|
|
22718
|
+
ruleId: "helper-usefulness",
|
|
22719
|
+
dimension: "delegation",
|
|
22720
|
+
scope,
|
|
22721
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
22722
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
22723
|
+
before: "dependent_action",
|
|
22724
|
+
dependentAction: null,
|
|
22725
|
+
acceptanceAllowed: true
|
|
22726
|
+
}));
|
|
22727
|
+
for (const constraint of input.constraints)
|
|
22728
|
+
for (const obligation of constraint.requires) {
|
|
22729
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
22730
|
+
requirements.push(requirement({
|
|
22731
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
22732
|
+
kind: obligation.kind,
|
|
22733
|
+
scope: normalizedScope(obligation.scope),
|
|
22734
|
+
refs: normalizedRefList(obligation.refs),
|
|
22735
|
+
method: obligation.method,
|
|
22736
|
+
before: obligation.before,
|
|
22737
|
+
dependentAction: obligation.dependentAction
|
|
22738
|
+
})}`,
|
|
22739
|
+
dimension,
|
|
22740
|
+
scope: obligation.scope,
|
|
22741
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
22742
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
22743
|
+
before: obligation.before,
|
|
22744
|
+
dependentAction: obligation.dependentAction,
|
|
22745
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
22746
|
+
}));
|
|
22069
22747
|
}
|
|
22070
|
-
|
|
22071
|
-
|
|
22072
|
-
if (
|
|
22748
|
+
const seen = new Set;
|
|
22749
|
+
return requirements.filter((item) => {
|
|
22750
|
+
if (seen.has(item.id))
|
|
22073
22751
|
return false;
|
|
22074
|
-
|
|
22075
|
-
return
|
|
22076
|
-
});
|
|
22077
|
-
if (requestedOutcome !== "stopped" && blocking.length)
|
|
22078
|
-
return failure2("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
22079
|
-
requirementIds: blocking.map((item) => item.requirementId),
|
|
22080
|
-
requirements: blocking.map((item) => {
|
|
22081
|
-
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
22082
|
-
return {
|
|
22083
|
-
requirementId: item.requirementId,
|
|
22084
|
-
ruleId: requirement?.ruleId ?? "unknown",
|
|
22085
|
-
reason: item.reason,
|
|
22086
|
-
satisfaction: requirement?.satisfaction ?? "",
|
|
22087
|
-
dependentAction: requirement?.dependentAction ?? null
|
|
22088
|
-
};
|
|
22089
|
-
})
|
|
22090
|
-
});
|
|
22091
|
-
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
22092
|
-
if (requestedOutcome === "verified" && accepted.length)
|
|
22093
|
-
return failure2("requirements_unsatisfied", "accepted limitations cannot be reported as verified", {
|
|
22094
|
-
requirementIds: accepted.map((item) => item.requirementId)
|
|
22095
|
-
});
|
|
22096
|
-
const evidenceIds = [
|
|
22097
|
-
...new Set([
|
|
22098
|
-
...evaluations.flatMap((item) => item.evidenceIds),
|
|
22099
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.evidenceIds ?? [])
|
|
22100
|
-
])
|
|
22101
|
-
];
|
|
22102
|
-
const decisionIds = [
|
|
22103
|
-
...new Set([
|
|
22104
|
-
...evaluations.flatMap((item) => item.decisionIds),
|
|
22105
|
-
...view.task.findings.flatMap((item) => item.data.resolution?.decisionIds ?? [])
|
|
22106
|
-
])
|
|
22107
|
-
];
|
|
22108
|
-
return success(null, null, {
|
|
22109
|
-
outcome: requestedOutcome === "stopped" ? "stopped" : accepted.length ? "accepted_limitations" : "verified",
|
|
22110
|
-
evidenceIds,
|
|
22111
|
-
decisionIds,
|
|
22112
|
-
requirementIds: evaluations.map((item) => item.requirementId)
|
|
22113
|
-
});
|
|
22114
|
-
}
|
|
22115
|
-
|
|
22116
|
-
// packages/workit-core/src/core/methods.ts
|
|
22117
|
-
var METHODS = {
|
|
22118
|
-
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
22119
|
-
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
22120
|
-
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
22121
|
-
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
22122
|
-
"workit-implement": { dimensions: ["delegation"] },
|
|
22123
|
-
"workit-debug": {},
|
|
22124
|
-
"workit-handoff": {},
|
|
22125
|
-
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
22126
|
-
};
|
|
22127
|
-
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
22128
|
-
var assuranceFor = (id, definition, capabilities) => {
|
|
22129
|
-
const keys = new Set([
|
|
22130
|
-
id,
|
|
22131
|
-
...definition.dimensions ?? [],
|
|
22132
|
-
...definition.ruleIds ?? []
|
|
22133
|
-
]);
|
|
22134
|
-
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
22135
|
-
if (id === "workit-review" && matches.length === 0)
|
|
22136
|
-
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
22137
|
-
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
22138
|
-
return {
|
|
22139
|
-
assurance: "unavailable",
|
|
22140
|
-
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
22141
|
-
};
|
|
22142
|
-
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
22143
|
-
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
22144
|
-
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
22145
|
-
};
|
|
22146
|
-
function selectMethods(policy, capabilities) {
|
|
22147
|
-
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
22148
|
-
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
22149
|
-
if (requirements.length === 0)
|
|
22150
|
-
return [];
|
|
22151
|
-
const capability = assuranceFor(id, definition, capabilities);
|
|
22152
|
-
const reasons = [
|
|
22153
|
-
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
22154
|
-
...capability.reasons
|
|
22155
|
-
];
|
|
22156
|
-
if (capability.assurance === "unavailable")
|
|
22157
|
-
reasons.unshift("required capability is unavailable");
|
|
22158
|
-
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
22752
|
+
seen.add(item.id);
|
|
22753
|
+
return true;
|
|
22159
22754
|
});
|
|
22160
22755
|
}
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
const
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
refs: []
|
|
22176
|
-
});
|
|
22177
|
-
return success(null, null, {
|
|
22178
|
-
candidate: candidate.data,
|
|
22179
|
-
staleEvidenceIds,
|
|
22180
|
-
workerUpdates: [],
|
|
22181
|
-
blockers,
|
|
22182
|
-
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
22183
|
-
});
|
|
22756
|
+
function resolvePolicy(input) {
|
|
22757
|
+
const normalized = normalizeResolverInput(input);
|
|
22758
|
+
if (!normalized.ok)
|
|
22759
|
+
return normalized;
|
|
22760
|
+
const requirements = resolveRequirements(normalized.data);
|
|
22761
|
+
const policy = {
|
|
22762
|
+
policyVersion: POLICY_VERSION,
|
|
22763
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
22764
|
+
requirements
|
|
22765
|
+
};
|
|
22766
|
+
const parsed = policySchema.safeParse(policy);
|
|
22767
|
+
if (!parsed.success)
|
|
22768
|
+
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
22769
|
+
return success(null, null, parsed.data);
|
|
22184
22770
|
}
|
|
22185
|
-
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
const
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
}
|
|
22198
|
-
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
|
|
22202
|
-
|
|
22203
|
-
|
|
22204
|
-
|
|
22205
|
-
};
|
|
22206
|
-
var compactReference = (ref) => {
|
|
22207
|
-
if (ref.kind === "file")
|
|
22208
|
-
return {
|
|
22209
|
-
kind: "file",
|
|
22210
|
-
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
22211
|
-
};
|
|
22212
|
-
if (ref.kind === "record")
|
|
22213
|
-
return {
|
|
22214
|
-
kind: "record",
|
|
22215
|
-
collection: ref.collection,
|
|
22216
|
-
id: ref.id
|
|
22217
|
-
};
|
|
22218
|
-
return { kind: ref.kind };
|
|
22219
|
-
};
|
|
22220
|
-
function compactTaskContext(view) {
|
|
22221
|
-
const decisions = view.task.decisions.slice().toSorted((left, right) => {
|
|
22222
|
-
const leftAt = compactTimestamp(left.recordedAt);
|
|
22223
|
-
const rightAt = compactTimestamp(right.recordedAt);
|
|
22224
|
-
if (leftAt !== rightAt)
|
|
22225
|
-
return leftAt > rightAt ? -1 : 1;
|
|
22226
|
-
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
22227
|
-
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
22228
|
-
id: entry.id,
|
|
22229
|
-
purpose: entry.data.purpose,
|
|
22230
|
-
status: entry.data.response,
|
|
22231
|
-
digest: entry.data.digest,
|
|
22232
|
-
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
22233
|
-
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
22234
|
-
}));
|
|
22235
|
-
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
22236
|
-
id: method.id,
|
|
22237
|
-
assurance: method.assurance,
|
|
22238
|
-
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
22239
|
-
})) : [];
|
|
22240
|
-
const gaps = Array.from(new Set([
|
|
22241
|
-
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
22242
|
-
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
22243
|
-
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
22244
|
-
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
22245
|
-
const context = {
|
|
22246
|
-
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
22247
|
-
status: view.task.status,
|
|
22248
|
-
decisions,
|
|
22249
|
-
methods,
|
|
22250
|
-
gaps,
|
|
22251
|
-
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
22771
|
+
function diffPolicy(previous, next, reason, now) {
|
|
22772
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
22773
|
+
throw new TypeError("previous policy is invalid");
|
|
22774
|
+
if (!policySchema.safeParse(next).success)
|
|
22775
|
+
throw new TypeError("next policy is invalid");
|
|
22776
|
+
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
22777
|
+
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
22778
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits3);
|
|
22779
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits3);
|
|
22780
|
+
if (added.length === 0 && retired.length === 0)
|
|
22781
|
+
return null;
|
|
22782
|
+
const normalizedReason = reason.trim();
|
|
22783
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
22784
|
+
const change = {
|
|
22785
|
+
recordedAt: now,
|
|
22786
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
22787
|
+
toInputDigest: next.inputDigest,
|
|
22788
|
+
added,
|
|
22789
|
+
retired,
|
|
22790
|
+
reason: changeReason
|
|
22252
22791
|
};
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
else if (context.decisions.length)
|
|
22258
|
-
context.decisions.pop();
|
|
22259
|
-
else
|
|
22260
|
-
context.methods.pop();
|
|
22261
|
-
encoded = JSON.stringify(context);
|
|
22262
|
-
}
|
|
22263
|
-
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
22264
|
-
context.objective = compactText(context.objective, 64) ?? "";
|
|
22265
|
-
context.nextAction = compactText(context.nextAction, 64);
|
|
22266
|
-
encoded = JSON.stringify(context);
|
|
22267
|
-
}
|
|
22268
|
-
return encoded;
|
|
22792
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
22793
|
+
if (!parsed.success)
|
|
22794
|
+
throw new TypeError("policy change is invalid");
|
|
22795
|
+
return parsed.data;
|
|
22269
22796
|
}
|
|
22270
|
-
var exportDigest = (bundle) => sha256({
|
|
22271
|
-
schemaVersion: bundle.schemaVersion,
|
|
22272
|
-
exportedAt: bundle.exportedAt,
|
|
22273
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
22274
|
-
task: bundle.task
|
|
22275
|
-
});
|
|
22276
22797
|
|
|
22277
22798
|
// packages/workit-core/src/core/task-engine.ts
|
|
22278
22799
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -24254,122 +24775,6 @@ class WorkitCore {
|
|
|
24254
24775
|
return this.summary(changed.data.task);
|
|
24255
24776
|
}
|
|
24256
24777
|
}
|
|
24257
|
-
|
|
24258
|
-
// packages/workit-core/src/core/session-context.ts
|
|
24259
|
-
var cache = new Map;
|
|
24260
|
-
// packages/workit-core/src/core/external-action.ts
|
|
24261
|
-
var chainStepSchema = union([
|
|
24262
|
-
string2().min(1),
|
|
24263
|
-
object2({ branch: string2().min(1) }).strict(),
|
|
24264
|
-
object2({ pr: literal(true) }).strict()
|
|
24265
|
-
]);
|
|
24266
|
-
var externalActionSchema = discriminatedUnion("operation", [
|
|
24267
|
-
object2({
|
|
24268
|
-
operation: literal("git.branch_setup"),
|
|
24269
|
-
payload: object2({
|
|
24270
|
-
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
24271
|
-
sdd_dir: string2().optional(),
|
|
24272
|
-
target_branch: string2().optional(),
|
|
24273
|
-
stash: _enum(["yes", "no"]).optional(),
|
|
24274
|
-
cwd: string2().min(1).optional()
|
|
24275
|
-
}).strict()
|
|
24276
|
-
}).strict(),
|
|
24277
|
-
object2({
|
|
24278
|
-
operation: literal("git.commit"),
|
|
24279
|
-
payload: object2({
|
|
24280
|
-
message: string2().min(1).optional(),
|
|
24281
|
-
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
24282
|
-
plan_branch: string2().min(1).optional(),
|
|
24283
|
-
cwd: string2().min(1).optional()
|
|
24284
|
-
}).strict()
|
|
24285
|
-
}).strict(),
|
|
24286
|
-
object2({
|
|
24287
|
-
operation: literal("git.push"),
|
|
24288
|
-
payload: object2({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
24289
|
-
}).strict(),
|
|
24290
|
-
object2({
|
|
24291
|
-
operation: literal("hosting.pull_request"),
|
|
24292
|
-
payload: object2({
|
|
24293
|
-
title: string2().min(1),
|
|
24294
|
-
body: string2().optional(),
|
|
24295
|
-
draft: boolean2().optional(),
|
|
24296
|
-
target_branch: string2().optional(),
|
|
24297
|
-
babysit: boolean2().optional(),
|
|
24298
|
-
cwd: string2().min(1).optional()
|
|
24299
|
-
}).strict()
|
|
24300
|
-
}).strict(),
|
|
24301
|
-
object2({
|
|
24302
|
-
operation: literal("hosting.merge"),
|
|
24303
|
-
payload: object2({
|
|
24304
|
-
target_branch: string2().min(1).optional(),
|
|
24305
|
-
source_branch: string2().min(1).optional(),
|
|
24306
|
-
cwd: string2().min(1).optional()
|
|
24307
|
-
}).strict()
|
|
24308
|
-
}).strict(),
|
|
24309
|
-
object2({
|
|
24310
|
-
operation: literal("hosting.delete_branch"),
|
|
24311
|
-
payload: object2({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
24312
|
-
}).strict(),
|
|
24313
|
-
object2({
|
|
24314
|
-
operation: literal("youtrack.update"),
|
|
24315
|
-
payload: object2({
|
|
24316
|
-
issueId: string2().min(1),
|
|
24317
|
-
markdown: string2().min(1),
|
|
24318
|
-
minutes: number2().positive().optional()
|
|
24319
|
-
}).strict()
|
|
24320
|
-
}).strict(),
|
|
24321
|
-
object2({
|
|
24322
|
-
operation: literal("youtrack.time"),
|
|
24323
|
-
payload: object2({
|
|
24324
|
-
issueId: string2().min(1),
|
|
24325
|
-
minutes: number2().positive(),
|
|
24326
|
-
text: string2().optional(),
|
|
24327
|
-
dateMs: number2().finite().optional()
|
|
24328
|
-
}).strict()
|
|
24329
|
-
}).strict(),
|
|
24330
|
-
object2({
|
|
24331
|
-
operation: literal("youtrack.meeting"),
|
|
24332
|
-
payload: object2({
|
|
24333
|
-
issueId: string2().min(1),
|
|
24334
|
-
minutes: number2().positive(),
|
|
24335
|
-
text: string2().min(1)
|
|
24336
|
-
}).strict()
|
|
24337
|
-
}).strict(),
|
|
24338
|
-
object2({
|
|
24339
|
-
operation: literal("changelog.apply"),
|
|
24340
|
-
payload: object2({
|
|
24341
|
-
entries: union([
|
|
24342
|
-
record(string2(), array(string2())),
|
|
24343
|
-
array(object2({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
24344
|
-
]).optional(),
|
|
24345
|
-
path: string2().optional(),
|
|
24346
|
-
normalize_only: boolean2().optional()
|
|
24347
|
-
}).strict()
|
|
24348
|
-
}).strict(),
|
|
24349
|
-
object2({
|
|
24350
|
-
operation: literal("context.read"),
|
|
24351
|
-
payload: object2({
|
|
24352
|
-
kind: _enum([
|
|
24353
|
-
"git",
|
|
24354
|
-
"pr",
|
|
24355
|
-
"youtrack",
|
|
24356
|
-
"github_issue",
|
|
24357
|
-
"gitlab_issue",
|
|
24358
|
-
"changelog",
|
|
24359
|
-
"release",
|
|
24360
|
-
"affected"
|
|
24361
|
-
]),
|
|
24362
|
-
range: string2().optional(),
|
|
24363
|
-
issueId: string2().optional(),
|
|
24364
|
-
issueUrl: string2().optional(),
|
|
24365
|
-
issueRef: string2().optional(),
|
|
24366
|
-
mode: string2().optional(),
|
|
24367
|
-
specPath: string2().optional(),
|
|
24368
|
-
planPath: string2().optional(),
|
|
24369
|
-
cwd: string2().min(1).optional()
|
|
24370
|
-
}).strict()
|
|
24371
|
-
}).strict()
|
|
24372
|
-
]);
|
|
24373
24778
|
// packages/workit-core/src/core.ts
|
|
24374
24779
|
var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
|
|
24375
24780
|
function gitRevisionParts(value) {
|
|
@@ -24413,7 +24818,7 @@ function resolveInside(root, candidate) {
|
|
|
24413
24818
|
}
|
|
24414
24819
|
function run2(root, executable, args, env = {}) {
|
|
24415
24820
|
const cwd = realpathSync7(root);
|
|
24416
|
-
const result =
|
|
24821
|
+
const result = spawnSync4(executable, args, {
|
|
24417
24822
|
cwd,
|
|
24418
24823
|
encoding: "utf8",
|
|
24419
24824
|
env: { ...process.env, ...env }
|
|
@@ -24484,9 +24889,9 @@ var applyPatterns = (value) => {
|
|
|
24484
24889
|
var redactSecrets = (value) => applyPatterns(value);
|
|
24485
24890
|
|
|
24486
24891
|
// packages/workit-core/src/core/pr-create.ts
|
|
24487
|
-
import
|
|
24892
|
+
import fs9 from "node:fs";
|
|
24488
24893
|
import path12 from "node:path";
|
|
24489
|
-
import { spawnSync as
|
|
24894
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24490
24895
|
function parseGhIssue(value) {
|
|
24491
24896
|
const m = /issues\/(\d+)/.exec(value);
|
|
24492
24897
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
@@ -24499,7 +24904,7 @@ function whichOnPath(tool) {
|
|
|
24499
24904
|
for (const name of names) {
|
|
24500
24905
|
const candidate = path12.join(dir, name);
|
|
24501
24906
|
try {
|
|
24502
|
-
|
|
24907
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
24503
24908
|
return candidate;
|
|
24504
24909
|
} catch {}
|
|
24505
24910
|
}
|
|
@@ -24508,7 +24913,7 @@ function whichOnPath(tool) {
|
|
|
24508
24913
|
}
|
|
24509
24914
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
24510
24915
|
var pushRemote = (cwd) => {
|
|
24511
|
-
const result =
|
|
24916
|
+
const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
24512
24917
|
cwd,
|
|
24513
24918
|
encoding: "utf8"
|
|
24514
24919
|
});
|
|
@@ -24519,7 +24924,7 @@ var pushRemote = (cwd) => {
|
|
|
24519
24924
|
};
|
|
24520
24925
|
|
|
24521
24926
|
// packages/workit-core/src/core/repo-context.ts
|
|
24522
|
-
import { spawnSync as
|
|
24927
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
24523
24928
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
24524
24929
|
import path13 from "node:path";
|
|
24525
24930
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
@@ -24548,7 +24953,7 @@ var invalidRange = (cwd) => ({
|
|
|
24548
24953
|
cwd
|
|
24549
24954
|
});
|
|
24550
24955
|
var runGit = (cwd, args) => {
|
|
24551
|
-
const result =
|
|
24956
|
+
const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
|
|
24552
24957
|
return {
|
|
24553
24958
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24554
24959
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24721,7 +25126,7 @@ var documentationFiles = (cwd) => {
|
|
|
24721
25126
|
walk(cwd, 1);
|
|
24722
25127
|
return matches.toSorted().slice(0, 200);
|
|
24723
25128
|
};
|
|
24724
|
-
var
|
|
25129
|
+
var readTrimmed2 = (file, maxLines) => {
|
|
24725
25130
|
if (!existsSync6(file))
|
|
24726
25131
|
return "";
|
|
24727
25132
|
const lines = readFileSync7(file, "utf8").split(`
|
|
@@ -24788,7 +25193,7 @@ ${status.stderr}` : "");
|
|
|
24788
25193
|
stdout += `template_path: ${template}
|
|
24789
25194
|
|
|
24790
25195
|
`;
|
|
24791
|
-
stdout +=
|
|
25196
|
+
stdout += readTrimmed2(path13.join(cwd, template), 220);
|
|
24792
25197
|
} else {
|
|
24793
25198
|
stdout += `template_path: none
|
|
24794
25199
|
|
|
@@ -24860,7 +25265,7 @@ function changelogContext(root, range) {
|
|
|
24860
25265
|
stdout += printSection("Existing CHANGELOG.md");
|
|
24861
25266
|
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
24862
25267
|
if (existsSync6(changelogPath)) {
|
|
24863
|
-
stdout +=
|
|
25268
|
+
stdout += readTrimmed2(changelogPath, 260) + `
|
|
24864
25269
|
`;
|
|
24865
25270
|
} else {
|
|
24866
25271
|
stdout += `CHANGELOG.md not found.
|
|
@@ -24902,7 +25307,7 @@ function docsRefreshContext(root, range) {
|
|
|
24902
25307
|
stdout += printSection("README Preview");
|
|
24903
25308
|
const readme = path13.join(cwd, "README.md");
|
|
24904
25309
|
if (existsSync6(readme)) {
|
|
24905
|
-
stdout +=
|
|
25310
|
+
stdout += readTrimmed2(readme, 220) + `
|
|
24906
25311
|
`;
|
|
24907
25312
|
} else {
|
|
24908
25313
|
stdout += `README.md not found.
|
|
@@ -24989,7 +25394,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
24989
25394
|
}
|
|
24990
25395
|
|
|
24991
25396
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
24992
|
-
import { spawnSync as
|
|
25397
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
24993
25398
|
var repoPathFromRemote = (remote) => {
|
|
24994
25399
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
24995
25400
|
if (!rest)
|
|
@@ -25010,7 +25415,7 @@ var defaultCli = (bin, available) => {
|
|
|
25010
25415
|
if (!available)
|
|
25011
25416
|
return null;
|
|
25012
25417
|
return (args, root) => {
|
|
25013
|
-
const out =
|
|
25418
|
+
const out = spawnSync7(bin, args, {
|
|
25014
25419
|
cwd: root,
|
|
25015
25420
|
encoding: "utf8",
|
|
25016
25421
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25088,7 +25493,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25088
25493
|
};
|
|
25089
25494
|
|
|
25090
25495
|
// packages/workit-core/src/core/youtrack.ts
|
|
25091
|
-
import
|
|
25496
|
+
import fs10 from "node:fs";
|
|
25092
25497
|
import path14 from "node:path";
|
|
25093
25498
|
|
|
25094
25499
|
// packages/workit-core/src/core/templates.ts
|
|
@@ -25107,17 +25512,17 @@ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? p
|
|
|
25107
25512
|
var youTrackTokenModeOk = (p) => {
|
|
25108
25513
|
if (process.platform === "win32")
|
|
25109
25514
|
return true;
|
|
25110
|
-
const mode =
|
|
25515
|
+
const mode = fs10.statSync(p).mode & 511;
|
|
25111
25516
|
return mode === 384;
|
|
25112
25517
|
};
|
|
25113
25518
|
function readYouTrackConfig(required) {
|
|
25114
25519
|
const cfgPath = youTrackConfigPath();
|
|
25115
|
-
if (!
|
|
25520
|
+
if (!fs10.existsSync(cfgPath)) {
|
|
25116
25521
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25117
25522
|
}
|
|
25118
25523
|
let parsed;
|
|
25119
25524
|
try {
|
|
25120
|
-
parsed = JSON.parse(
|
|
25525
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25121
25526
|
} catch {
|
|
25122
25527
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25123
25528
|
}
|
|
@@ -25128,11 +25533,11 @@ function readYouTrackConfig(required) {
|
|
|
25128
25533
|
}
|
|
25129
25534
|
var readYouTrackContextConfig = () => {
|
|
25130
25535
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25131
|
-
if (!
|
|
25536
|
+
if (!fs10.existsSync(cfgPath))
|
|
25132
25537
|
return { data: {} };
|
|
25133
25538
|
let parsed;
|
|
25134
25539
|
try {
|
|
25135
|
-
parsed = JSON.parse(
|
|
25540
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25136
25541
|
} catch {
|
|
25137
25542
|
return { error: "YouTrack configuration is unavailable" };
|
|
25138
25543
|
}
|
|
@@ -25172,11 +25577,11 @@ var youTrackToken = () => {
|
|
|
25172
25577
|
return loaded;
|
|
25173
25578
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25174
25579
|
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
25175
|
-
if (!tokenPath || !
|
|
25580
|
+
if (!tokenPath || !fs10.existsSync(tokenPath))
|
|
25176
25581
|
return { error: "missing youtrack.token" };
|
|
25177
25582
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25178
25583
|
return { error: "youtrack.token mode must be 0600" };
|
|
25179
|
-
const token =
|
|
25584
|
+
const token = fs10.readFileSync(tokenPath, "utf8").trim();
|
|
25180
25585
|
if (!token)
|
|
25181
25586
|
return { error: "empty token file" };
|
|
25182
25587
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25241,9 +25646,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25241
25646
|
} catch {
|
|
25242
25647
|
continue;
|
|
25243
25648
|
}
|
|
25244
|
-
if (!
|
|
25649
|
+
if (!fs10.existsSync(full))
|
|
25245
25650
|
continue;
|
|
25246
|
-
const text =
|
|
25651
|
+
const text = fs10.readFileSync(full, "utf8");
|
|
25247
25652
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25248
25653
|
if (m)
|
|
25249
25654
|
return m[1];
|
|
@@ -25804,72 +26209,168 @@ var main = async () => {
|
|
|
25804
26209
|
var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
|
|
25805
26210
|
if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
|
|
25806
26211
|
await main();
|
|
26212
|
+
// packages/workit-core/src/hooks/descriptor.ts
|
|
26213
|
+
var support = (descriptor, axis) => {
|
|
26214
|
+
if (axis.startsWith("event:"))
|
|
26215
|
+
return descriptor.events[axis.slice("event:".length)].support;
|
|
26216
|
+
if (axis === "shellPolicy.deny")
|
|
26217
|
+
return descriptor.shellPolicy.deny;
|
|
26218
|
+
if (axis === "stopControl" || axis === "shellAvailable")
|
|
26219
|
+
return descriptor[axis];
|
|
26220
|
+
const [group, key] = axis.split(".");
|
|
26221
|
+
return descriptor[group][key];
|
|
26222
|
+
};
|
|
26223
|
+
var usable = (value) => value === "native" || value === "partial";
|
|
26224
|
+
var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map((rule) => {
|
|
26225
|
+
const levels = rule.requires.map((axis) => support(descriptor, axis));
|
|
26226
|
+
const available = rule.assurance !== "unavailable" && levels.every(usable) && (rule.observed ?? []).every((flag) => observed[flag] === true);
|
|
26227
|
+
const assurance = !available ? "unavailable" : rule.assurance === "enforced" && (levels.length === 0 || levels.some((level) => level !== "native")) ? "agent_guided" : rule.assurance;
|
|
26228
|
+
return {
|
|
26229
|
+
name: rule.name,
|
|
26230
|
+
surface: rule.surface,
|
|
26231
|
+
assurance,
|
|
26232
|
+
reason: assurance === "unavailable" ? rule.unavailableReason ?? rule.reason : rule.reason,
|
|
26233
|
+
refs: rule.refs.map((handle) => ({ kind: "host", host: descriptor.host, handle }))
|
|
26234
|
+
};
|
|
26235
|
+
});
|
|
26236
|
+
// packages/workit-core/src/hooks/context.ts
|
|
26237
|
+
var cache = new Map;
|
|
26238
|
+
// packages/workit-core/src/hooks/handle.ts
|
|
26239
|
+
var offered = new Set;
|
|
26240
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
26241
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
25807
26242
|
|
|
25808
|
-
// packages/workit-
|
|
25809
|
-
var
|
|
25810
|
-
var
|
|
25811
|
-
|
|
25812
|
-
|
|
26243
|
+
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
26244
|
+
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
26245
|
+
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
26246
|
+
var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
|
|
26247
|
+
|
|
26248
|
+
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
26249
|
+
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
26250
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
26251
|
+
|
|
26252
|
+
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
26253
|
+
var undocumented = { support: "undocumented", native: null };
|
|
26254
|
+
var none = { support: "none", native: null };
|
|
26255
|
+
var CURSOR_DESCRIPTOR = {
|
|
26256
|
+
host: "cursor",
|
|
26257
|
+
label: "Cursor",
|
|
26258
|
+
verifiedAgainst: "Cursor hooks docs; payloads in test/fixtures/hooks/cursor",
|
|
26259
|
+
docs: ["https://cursor.com/docs/agent/hooks"],
|
|
26260
|
+
transport: "hook-process",
|
|
26261
|
+
events: {
|
|
26262
|
+
"session.start": { support: "native", native: "sessionStart" },
|
|
26263
|
+
"context.turn": none,
|
|
26264
|
+
"shell.pre": { support: "native", native: "beforeShellExecution" },
|
|
26265
|
+
"tool.pre": { support: "native", native: "preToolUse" },
|
|
26266
|
+
"shell.post": undocumented,
|
|
26267
|
+
"subagent.start": { support: "native", native: "subagentStart" },
|
|
26268
|
+
"subagent.stop": { support: "partial", native: "subagentStop" },
|
|
26269
|
+
"prompt.submit": undocumented,
|
|
26270
|
+
"compact.pre": { support: "partial", native: "preCompact" },
|
|
26271
|
+
stop: undocumented
|
|
26272
|
+
},
|
|
26273
|
+
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
|
|
26274
|
+
context: {
|
|
26275
|
+
sessionStart: "native",
|
|
26276
|
+
perTurn: "none",
|
|
26277
|
+
afterCompact: "partial",
|
|
26278
|
+
task: "single-active"
|
|
26279
|
+
},
|
|
26280
|
+
subagents: {
|
|
26281
|
+
identity: "native",
|
|
26282
|
+
parentBinding: "native",
|
|
26283
|
+
blockStart: "native",
|
|
26284
|
+
worktreeIsolation: "undocumented",
|
|
26285
|
+
maxConcurrency: "undocumented"
|
|
26286
|
+
},
|
|
26287
|
+
provenance: {
|
|
26288
|
+
sessionId: "native",
|
|
26289
|
+
agentIdOnTool: "undocumented",
|
|
26290
|
+
postToolObserve: "undocumented"
|
|
26291
|
+
},
|
|
26292
|
+
interaction: { questions: "none", writeBoundary: "partial" },
|
|
26293
|
+
stopControl: "undocumented",
|
|
26294
|
+
shellAvailable: "native",
|
|
26295
|
+
perEventCost: "npx-network",
|
|
26296
|
+
capabilities: [
|
|
25813
26297
|
{
|
|
25814
26298
|
name: "interactive_decision",
|
|
25815
26299
|
surface: "AskQuestion",
|
|
26300
|
+
refs: ["AskQuestion"],
|
|
26301
|
+
requires: [],
|
|
25816
26302
|
assurance: "agent_guided",
|
|
25817
|
-
reason: "Cursor does not expose AskQuestion answers to Workit hooks or MCP"
|
|
25818
|
-
refs: [hostRef("AskQuestion")]
|
|
26303
|
+
reason: "Cursor does not expose AskQuestion answers to Workit hooks or MCP"
|
|
25819
26304
|
},
|
|
25820
26305
|
{
|
|
25821
26306
|
name: "known_product_writes",
|
|
25822
26307
|
surface: "preToolUse",
|
|
26308
|
+
refs: ["preToolUse"],
|
|
26309
|
+
requires: [],
|
|
25823
26310
|
assurance: "unavailable",
|
|
25824
|
-
reason: "file writes are host-policy; the Cursor hook no longer gates write tools or shell commands"
|
|
25825
|
-
refs: [hostRef("preToolUse")]
|
|
26311
|
+
reason: "file writes are host-policy; the Cursor hook no longer gates write tools or shell commands"
|
|
25826
26312
|
},
|
|
25827
26313
|
{
|
|
25828
26314
|
name: "native_subagents",
|
|
25829
26315
|
surface: "subagentStart/subagentStop",
|
|
25830
|
-
|
|
25831
|
-
|
|
25832
|
-
|
|
26316
|
+
refs: ["subagentStart", "subagentStop"],
|
|
26317
|
+
requires: ["event:subagent.start", "event:subagent.stop"],
|
|
26318
|
+
observed: ["subagent.start", "subagent.stop"],
|
|
26319
|
+
assurance: "agent_guided",
|
|
26320
|
+
reason: "reviewer/investigator starts are bounded; Cursor implementer delegation is unavailable and subagentStop lacks a stable child identity",
|
|
26321
|
+
unavailableReason: "Cursor native subagent lifecycle hooks are incomplete"
|
|
25833
26322
|
},
|
|
25834
26323
|
{
|
|
25835
26324
|
name: "native_subagent_start",
|
|
25836
26325
|
surface: "subagentStart",
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
|
|
26326
|
+
refs: ["subagentStart"],
|
|
26327
|
+
requires: ["event:subagent.start", "subagents.blockStart"],
|
|
26328
|
+
observed: ["subagent.start"],
|
|
26329
|
+
assurance: "enforced",
|
|
26330
|
+
reason: "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable",
|
|
26331
|
+
unavailableReason: "Cursor subagentStart is absent"
|
|
25840
26332
|
},
|
|
25841
26333
|
{
|
|
25842
26334
|
name: "fresh-context-review",
|
|
25843
26335
|
surface: "subagentStart",
|
|
25844
|
-
|
|
25845
|
-
|
|
25846
|
-
|
|
26336
|
+
refs: ["subagentStart"],
|
|
26337
|
+
requires: ["event:subagent.start"],
|
|
26338
|
+
observed: ["subagent.start"],
|
|
26339
|
+
assurance: "agent_guided",
|
|
26340
|
+
reason: "independent review runs as a bounded reviewer subagent; stops lack stable identity, so reviewer exclusivity is evaluated from recorded evidence",
|
|
26341
|
+
unavailableReason: "Cursor subagentStart is unavailable for independent review"
|
|
25847
26342
|
},
|
|
25848
26343
|
{
|
|
25849
26344
|
name: "arbitrary_shell_write",
|
|
25850
26345
|
surface: "unobservable_shell",
|
|
26346
|
+
refs: ["beforeShellExecution"],
|
|
26347
|
+
requires: [],
|
|
25851
26348
|
assurance: "unavailable",
|
|
25852
|
-
reason: "Only explicitly parsed shell targets are interceptable; arbitrary shell writes are not provable"
|
|
25853
|
-
refs: [hostRef("beforeShellExecution")]
|
|
26349
|
+
reason: "Only explicitly parsed shell targets are interceptable; arbitrary shell writes are not provable"
|
|
25854
26350
|
},
|
|
25855
26351
|
{
|
|
25856
26352
|
name: "compact_context",
|
|
25857
26353
|
surface: "sessionStart/preCompact",
|
|
25858
|
-
|
|
25859
|
-
|
|
25860
|
-
|
|
26354
|
+
refs: ["sessionStart", "preCompact"],
|
|
26355
|
+
requires: ["context.sessionStart"],
|
|
26356
|
+
observed: ["session.start"],
|
|
26357
|
+
assurance: "agent_guided",
|
|
26358
|
+
reason: "sessionStart injects context; preCompact can only show a bounded user reminder"
|
|
25861
26359
|
}
|
|
25862
|
-
]
|
|
26360
|
+
]
|
|
25863
26361
|
};
|
|
25864
|
-
var
|
|
25865
|
-
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
"
|
|
25871
|
-
|
|
25872
|
-
|
|
26362
|
+
var BLOCKING = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
26363
|
+
|
|
26364
|
+
// packages/workit-core/src/hooks/run.ts
|
|
26365
|
+
if (false) {}
|
|
26366
|
+
// packages/workit-cursor/hooks/workit-hook.ts
|
|
26367
|
+
var cursorCapabilities = (availability = {}) => capabilitiesFor(CURSOR_DESCRIPTOR, {
|
|
26368
|
+
"session.start": availability.sessionStart,
|
|
26369
|
+
"tool.pre": availability.preToolUse,
|
|
26370
|
+
"shell.pre": availability.beforeShellExecution,
|
|
26371
|
+
"subagent.start": availability.subagentStart,
|
|
26372
|
+
"subagent.stop": availability.subagentStop
|
|
26373
|
+
});
|
|
25873
26374
|
if (false)
|
|
25874
26375
|
;
|
|
25875
26376
|
// packages/workit-cursor/mcp/run-server.ts
|