@brainervirus/workit-cursor 2.1.5 → 2.2.1
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 +3910 -2640
- package/dist/mcp-server.js +1528 -946
- package/dist/workit-hook.js +3912 -2613
- 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
|
|
@@ -18848,7 +19061,7 @@ class TaskStore {
|
|
|
18848
19061
|
if (!current.data)
|
|
18849
19062
|
return failure2("not_found", "workspace not found");
|
|
18850
19063
|
if (current.data.revision !== expected)
|
|
18851
|
-
return this.
|
|
19064
|
+
return this.workspaceConflict(expected, current.data.revision);
|
|
18852
19065
|
const previousBytes = this.snapshotBytes(this.workspacePath);
|
|
18853
19066
|
if (!previousBytes)
|
|
18854
19067
|
return failure2("storage_error", "workspace snapshot disappeared during mutation");
|
|
@@ -18891,7 +19104,7 @@ class TaskStore {
|
|
|
18891
19104
|
if (task.data.revision !== expected)
|
|
18892
19105
|
return this.conflict(expected, task.data.revision);
|
|
18893
19106
|
if (workspace.data.revision !== input.expectedWorkspaceRevision)
|
|
18894
|
-
return this.
|
|
19107
|
+
return this.workspaceConflict(input.expectedWorkspaceRevision, workspace.data.revision);
|
|
18895
19108
|
const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
|
|
18896
19109
|
const previousTaskBytes = this.snapshotBytes(this.taskPath(input.taskId));
|
|
18897
19110
|
if (!previousWorkspaceBytes || !previousTaskBytes)
|
|
@@ -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,27 +19895,28 @@ 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,
|
|
19507
19914
|
actualRevision: actual
|
|
19508
19915
|
});
|
|
19509
19916
|
}
|
|
19917
|
+
workspaceConflict(expected, actual) {
|
|
19918
|
+
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
19919
|
+
}
|
|
19510
19920
|
taskPath(taskId) {
|
|
19511
19921
|
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
19512
19922
|
}
|
|
@@ -19532,53 +19942,116 @@ class TaskStore {
|
|
|
19532
19942
|
return path4.join(this.workitDir, ".gitignore");
|
|
19533
19943
|
}
|
|
19534
19944
|
}
|
|
19535
|
-
// packages/workit-core/src/core/
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
|
|
19539
|
-
|
|
19540
|
-
|
|
19541
|
-
|
|
19542
|
-
|
|
19543
|
-
// packages/workit-core/src/core/auto-approval.ts
|
|
19544
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
19545
|
-
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
19546
|
-
import path8 from "node:path";
|
|
19547
|
-
|
|
19548
|
-
// packages/workit-core/src/core/workspaces.ts
|
|
19549
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
19550
|
-
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
19551
|
-
import path6 from "node:path";
|
|
19552
|
-
|
|
19553
|
-
// packages/workit-core/src/core/config.ts
|
|
19554
|
-
import {
|
|
19555
|
-
copyFileSync,
|
|
19556
|
-
cpSync,
|
|
19557
|
-
existsSync as existsSync3,
|
|
19558
|
-
mkdirSync as mkdirSync2,
|
|
19559
|
-
readFileSync as readFileSync2,
|
|
19560
|
-
readdirSync as readdirSync2,
|
|
19561
|
-
writeFileSync as writeFileSync2
|
|
19562
|
-
} from "node:fs";
|
|
19563
|
-
import os from "node:os";
|
|
19564
|
-
import path5 from "node:path";
|
|
19565
|
-
|
|
19566
|
-
// packages/workit-core/src/core/boundary.ts
|
|
19567
|
-
import { statSync as statSync2 } from "node:fs";
|
|
19568
|
-
var EVENT = {
|
|
19569
|
-
initialization: "initialization",
|
|
19570
|
-
provenance: "provenance",
|
|
19571
|
-
assets: "assets",
|
|
19572
|
-
configurationSource: "configuration_source",
|
|
19573
|
-
hooks: "hooks",
|
|
19574
|
-
mcpConnection: "mcp_connection",
|
|
19575
|
-
migration: "migration",
|
|
19576
|
-
installSteps: "install_steps",
|
|
19577
|
-
uncaughtFailure: "uncaught_failure",
|
|
19578
|
-
toolsFailed: "tools_failed",
|
|
19579
|
-
doctor: "doctor"
|
|
19945
|
+
// packages/workit-core/src/core/workers.ts
|
|
19946
|
+
var same = (left, right) => {
|
|
19947
|
+
try {
|
|
19948
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
19949
|
+
} catch {
|
|
19950
|
+
return false;
|
|
19951
|
+
}
|
|
19580
19952
|
};
|
|
19581
|
-
var
|
|
19953
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
19954
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
19955
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
19956
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
19957
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
19958
|
+
const code = char.charCodeAt(0);
|
|
19959
|
+
return code < 32 || code === 127;
|
|
19960
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
19961
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
19962
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
19963
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
19964
|
+
const owner = workspace.writer?.owner;
|
|
19965
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
19966
|
+
};
|
|
19967
|
+
function assertProductWriteAllowed(input) {
|
|
19968
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
19969
|
+
return failure2("invalid_input", "invalid product write path");
|
|
19970
|
+
if (!input.store)
|
|
19971
|
+
return failure2("permission_denied", "write authorization requires core state");
|
|
19972
|
+
const task = input.store.readTask(input.task.id);
|
|
19973
|
+
if (!task.ok)
|
|
19974
|
+
return task;
|
|
19975
|
+
const workspace = input.store.readWorkspace();
|
|
19976
|
+
if (!workspace.ok)
|
|
19977
|
+
return workspace;
|
|
19978
|
+
if (!workspace.data)
|
|
19979
|
+
return failure2("not_found", "workspace not found");
|
|
19980
|
+
const workspaceRecord = workspace.data;
|
|
19981
|
+
if (task.data.status !== "active")
|
|
19982
|
+
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
19983
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
19984
|
+
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
19985
|
+
const writer = workspaceRecord.writer;
|
|
19986
|
+
if (!writer)
|
|
19987
|
+
return failure2("permission_denied", "checkout has no writer owner");
|
|
19988
|
+
if (writer.state === "uncertain")
|
|
19989
|
+
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
19990
|
+
owner: writer.owner
|
|
19991
|
+
});
|
|
19992
|
+
const caller = callerValue(input.caller);
|
|
19993
|
+
const workerId = workerIdOf(input.caller);
|
|
19994
|
+
const owner = writer.owner;
|
|
19995
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
19996
|
+
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
19997
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
19998
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
19999
|
+
return failure2("permission_denied", "only implementers can own product writes");
|
|
20000
|
+
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))))
|
|
20001
|
+
return failure2("recovery_required", "worker session is not an active writer");
|
|
20002
|
+
return success(null, workspaceRecord.revision, owner);
|
|
20003
|
+
}
|
|
20004
|
+
|
|
20005
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
20006
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
20007
|
+
import * as fs8 from "node:fs";
|
|
20008
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
20009
|
+
import path10 from "node:path";
|
|
20010
|
+
|
|
20011
|
+
// packages/workit-core/src/core/authority.ts
|
|
20012
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
20013
|
+
import * as fs7 from "node:fs";
|
|
20014
|
+
import path9 from "node:path";
|
|
20015
|
+
|
|
20016
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
20017
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
20018
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
20019
|
+
import path8 from "node:path";
|
|
20020
|
+
|
|
20021
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
20022
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
20023
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
20024
|
+
import path6 from "node:path";
|
|
20025
|
+
|
|
20026
|
+
// packages/workit-core/src/core/config.ts
|
|
20027
|
+
import {
|
|
20028
|
+
copyFileSync,
|
|
20029
|
+
cpSync,
|
|
20030
|
+
existsSync as existsSync3,
|
|
20031
|
+
mkdirSync as mkdirSync2,
|
|
20032
|
+
readFileSync as readFileSync2,
|
|
20033
|
+
readdirSync as readdirSync2,
|
|
20034
|
+
writeFileSync as writeFileSync2
|
|
20035
|
+
} from "node:fs";
|
|
20036
|
+
import os from "node:os";
|
|
20037
|
+
import path5 from "node:path";
|
|
20038
|
+
|
|
20039
|
+
// packages/workit-core/src/core/boundary.ts
|
|
20040
|
+
import { statSync as statSync2 } from "node:fs";
|
|
20041
|
+
var EVENT = {
|
|
20042
|
+
initialization: "initialization",
|
|
20043
|
+
provenance: "provenance",
|
|
20044
|
+
assets: "assets",
|
|
20045
|
+
configurationSource: "configuration_source",
|
|
20046
|
+
hooks: "hooks",
|
|
20047
|
+
mcpConnection: "mcp_connection",
|
|
20048
|
+
migration: "migration",
|
|
20049
|
+
installSteps: "install_steps",
|
|
20050
|
+
uncaughtFailure: "uncaught_failure",
|
|
20051
|
+
toolsFailed: "tools_failed",
|
|
20052
|
+
doctor: "doctor"
|
|
20053
|
+
};
|
|
20054
|
+
var errorDetail = (err) => {
|
|
19582
20055
|
if (err instanceof Error) {
|
|
19583
20056
|
return { error_name: err.name, error: err.message };
|
|
19584
20057
|
}
|
|
@@ -20207,13 +20680,13 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
20207
20680
|
};
|
|
20208
20681
|
|
|
20209
20682
|
// packages/workit-core/src/core/vcs-config.ts
|
|
20210
|
-
import
|
|
20683
|
+
import fs6 from "node:fs";
|
|
20211
20684
|
import path7 from "node:path";
|
|
20212
|
-
import { spawnSync } from "node:child_process";
|
|
20685
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
20213
20686
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
20214
20687
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
20215
20688
|
var remoteProvider = (cwd) => {
|
|
20216
|
-
const r =
|
|
20689
|
+
const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
20217
20690
|
if (r.status !== 0)
|
|
20218
20691
|
return null;
|
|
20219
20692
|
const url = (r.stdout ?? "").trim();
|
|
@@ -20250,7 +20723,7 @@ var remoteEndpoint = (remote) => {
|
|
|
20250
20723
|
var configuredSshEndpoint = (host) => {
|
|
20251
20724
|
if (host.startsWith("-"))
|
|
20252
20725
|
return null;
|
|
20253
|
-
const resolved =
|
|
20726
|
+
const resolved = spawnSync2("ssh", ["-G", host], {
|
|
20254
20727
|
encoding: "utf8",
|
|
20255
20728
|
timeout: 5000,
|
|
20256
20729
|
maxBuffer: 1024 * 1024,
|
|
@@ -20300,7 +20773,7 @@ var readVcsConfig = () => {
|
|
|
20300
20773
|
const file = vcsConfigPath();
|
|
20301
20774
|
let raw;
|
|
20302
20775
|
try {
|
|
20303
|
-
raw =
|
|
20776
|
+
raw = fs6.readFileSync(file, "utf8");
|
|
20304
20777
|
} catch {
|
|
20305
20778
|
return { status: "missing", path: file, config: {} };
|
|
20306
20779
|
}
|
|
@@ -20401,7 +20874,7 @@ function vcsCliIdentity(cwd) {
|
|
|
20401
20874
|
PATH: process.env.PATH ?? "",
|
|
20402
20875
|
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
20403
20876
|
};
|
|
20404
|
-
const result =
|
|
20877
|
+
const result = spawnSync2(bin, ["api", "user"], {
|
|
20405
20878
|
cwd: root,
|
|
20406
20879
|
encoding: "utf8",
|
|
20407
20880
|
env
|
|
@@ -20443,7 +20916,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20443
20916
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
20444
20917
|
descriptionPreview: desc.slice(0, 600)
|
|
20445
20918
|
});
|
|
20446
|
-
const remoteResult =
|
|
20919
|
+
const remoteResult = spawnSync2("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
20447
20920
|
cwd,
|
|
20448
20921
|
encoding: "utf8"
|
|
20449
20922
|
});
|
|
@@ -20459,7 +20932,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20459
20932
|
if (!hostingApiHostMatches(remote, host))
|
|
20460
20933
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
20461
20934
|
if (provider === "gitlab") {
|
|
20462
|
-
const run = (args) =>
|
|
20935
|
+
const run = (args) => spawnSync2("glab", ["api", ...args], {
|
|
20463
20936
|
cwd,
|
|
20464
20937
|
encoding: "utf8",
|
|
20465
20938
|
env
|
|
@@ -20479,7 +20952,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
20479
20952
|
});
|
|
20480
20953
|
}
|
|
20481
20954
|
} else if (provider === "github") {
|
|
20482
|
-
const r =
|
|
20955
|
+
const r = spawnSync2("gh", [
|
|
20483
20956
|
"pr",
|
|
20484
20957
|
"list",
|
|
20485
20958
|
"--repo",
|
|
@@ -20538,66 +21011,6 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
20538
21011
|
return { ok: true };
|
|
20539
21012
|
};
|
|
20540
21013
|
|
|
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
21014
|
// packages/workit-core/src/core/auto-approval.ts
|
|
20602
21015
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
20603
21016
|
var OPERATION_CLASS = {
|
|
@@ -20884,7 +21297,7 @@ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
|
20884
21297
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
20885
21298
|
let root;
|
|
20886
21299
|
try {
|
|
20887
|
-
root =
|
|
21300
|
+
root = fs7.realpathSync(checkoutRoot);
|
|
20888
21301
|
} catch {
|
|
20889
21302
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
20890
21303
|
}
|
|
@@ -20907,8 +21320,8 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
20907
21320
|
if (!segment)
|
|
20908
21321
|
continue;
|
|
20909
21322
|
current = path9.join(current, segment);
|
|
20910
|
-
const stat =
|
|
20911
|
-
const real =
|
|
21323
|
+
const stat = fs7.lstatSync(current);
|
|
21324
|
+
const real = fs7.realpathSync(current);
|
|
20912
21325
|
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
20913
21326
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
20914
21327
|
fields: [
|
|
@@ -20925,7 +21338,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
20925
21338
|
if (current !== target && !stat.isDirectory())
|
|
20926
21339
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
20927
21340
|
}
|
|
20928
|
-
if (digestBytes2(
|
|
21341
|
+
if (digestBytes2(fs7.readFileSync(target)) !== reference.digest)
|
|
20929
21342
|
return failure2("permission_denied", "approved document bytes have changed");
|
|
20930
21343
|
} catch {
|
|
20931
21344
|
return failure2("invalid_input", "approved document is unavailable");
|
|
@@ -21288,18 +21701,41 @@ function reconcileAction(input) {
|
|
|
21288
21701
|
var bindingCovers = scopeCovers;
|
|
21289
21702
|
var verifyDecisionContent = verifyContentRefs;
|
|
21290
21703
|
|
|
21291
|
-
// packages/workit-core/src/core/
|
|
21292
|
-
var
|
|
21293
|
-
|
|
21294
|
-
|
|
21295
|
-
|
|
21296
|
-
|
|
21297
|
-
|
|
21298
|
-
|
|
21299
|
-
|
|
21300
|
-
|
|
21301
|
-
|
|
21302
|
-
|
|
21704
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
21705
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
21706
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
21707
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
21708
|
+
var canonicalPath = (value) => {
|
|
21709
|
+
if (typeof value !== "string" || !value)
|
|
21710
|
+
return null;
|
|
21711
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
21712
|
+
return null;
|
|
21713
|
+
const normalized = path10.posix.normalize(value);
|
|
21714
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
21715
|
+
return null;
|
|
21716
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
21717
|
+
};
|
|
21718
|
+
var canonicalScope = (scope) => {
|
|
21719
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
21720
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
21721
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
21722
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
21723
|
+
return null;
|
|
21724
|
+
return {
|
|
21725
|
+
description: scope.description,
|
|
21726
|
+
paths: [...new Set(normalizedPaths)],
|
|
21727
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
21728
|
+
};
|
|
21729
|
+
};
|
|
21730
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
21731
|
+
var scopeMatches = (value, scope) => {
|
|
21732
|
+
const normalized = canonicalScope(scope);
|
|
21733
|
+
if (!normalized)
|
|
21734
|
+
return false;
|
|
21735
|
+
const paths = normalized.paths;
|
|
21736
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
21737
|
+
};
|
|
21738
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
21303
21739
|
var compareCodeUnits2 = (left, right) => {
|
|
21304
21740
|
const length = Math.min(left.length, right.length);
|
|
21305
21741
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -21309,362 +21745,8 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
21309
21745
|
}
|
|
21310
21746
|
return left.length - right.length;
|
|
21311
21747
|
};
|
|
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
21748
|
var gitPaths = (root) => {
|
|
21667
|
-
const result =
|
|
21749
|
+
const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
21668
21750
|
cwd: root,
|
|
21669
21751
|
encoding: "buffer"
|
|
21670
21752
|
});
|
|
@@ -21677,7 +21759,7 @@ var gitPaths = (root) => {
|
|
|
21677
21759
|
};
|
|
21678
21760
|
}
|
|
21679
21761
|
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
21680
|
-
const stagedDeleted =
|
|
21762
|
+
const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
21681
21763
|
if (stagedDeleted.status !== 0) {
|
|
21682
21764
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
21683
21765
|
if (!/not a git repository/i.test(stderr))
|
|
@@ -21690,7 +21772,7 @@ var gitPaths = (root) => {
|
|
|
21690
21772
|
var walk = (root, directory, output) => {
|
|
21691
21773
|
let names;
|
|
21692
21774
|
try {
|
|
21693
|
-
names =
|
|
21775
|
+
names = fs8.readdirSync(directory);
|
|
21694
21776
|
} catch {
|
|
21695
21777
|
return true;
|
|
21696
21778
|
}
|
|
@@ -21701,7 +21783,7 @@ var walk = (root, directory, output) => {
|
|
|
21701
21783
|
const target = path10.join(directory, name);
|
|
21702
21784
|
let stat;
|
|
21703
21785
|
try {
|
|
21704
|
-
stat =
|
|
21786
|
+
stat = fs8.lstatSync(target);
|
|
21705
21787
|
} catch {
|
|
21706
21788
|
uncertain = true;
|
|
21707
21789
|
continue;
|
|
@@ -21720,10 +21802,10 @@ var scopeRoots = (root, scope) => {
|
|
|
21720
21802
|
if (!inside(root, target))
|
|
21721
21803
|
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
21804
|
let ancestor = target;
|
|
21723
|
-
while (!
|
|
21805
|
+
while (!fs8.existsSync(ancestor) && ancestor !== root)
|
|
21724
21806
|
ancestor = path10.dirname(ancestor);
|
|
21725
21807
|
try {
|
|
21726
|
-
if (!inside(root,
|
|
21808
|
+
if (!inside(root, fs8.realpathSync(ancestor)))
|
|
21727
21809
|
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
21810
|
} catch {
|
|
21729
21811
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -21735,7 +21817,7 @@ var scopeRoots = (root, scope) => {
|
|
|
21735
21817
|
function captureCandidate(root, scope, environment = []) {
|
|
21736
21818
|
let checkout;
|
|
21737
21819
|
try {
|
|
21738
|
-
checkout =
|
|
21820
|
+
checkout = fs8.realpathSync(root);
|
|
21739
21821
|
} catch {
|
|
21740
21822
|
return failure2("invalid_input", "candidate root does not exist");
|
|
21741
21823
|
}
|
|
@@ -21753,11 +21835,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21753
21835
|
if (item === ".git" || item === ".workit")
|
|
21754
21836
|
continue;
|
|
21755
21837
|
names.add(item);
|
|
21756
|
-
if (!
|
|
21838
|
+
if (!fs8.existsSync(target))
|
|
21757
21839
|
continue;
|
|
21758
21840
|
let stat;
|
|
21759
21841
|
try {
|
|
21760
|
-
stat =
|
|
21842
|
+
stat = fs8.lstatSync(target);
|
|
21761
21843
|
} catch {
|
|
21762
21844
|
uncertain = true;
|
|
21763
21845
|
continue;
|
|
@@ -21775,15 +21857,15 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21775
21857
|
continue;
|
|
21776
21858
|
const target = path10.join(checkout, item);
|
|
21777
21859
|
try {
|
|
21778
|
-
const stat =
|
|
21860
|
+
const stat = fs8.lstatSync(target);
|
|
21779
21861
|
if (stat.isSymbolicLink()) {
|
|
21780
|
-
const link =
|
|
21862
|
+
const link = fs8.readlinkSync(target);
|
|
21781
21863
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
21782
21864
|
} else if (stat.isFile()) {
|
|
21783
21865
|
files.push({
|
|
21784
21866
|
path: item,
|
|
21785
21867
|
kind: "file",
|
|
21786
|
-
digest: digestBytes3(
|
|
21868
|
+
digest: digestBytes3(fs8.readFileSync(target)),
|
|
21787
21869
|
executable: (stat.mode & 73) !== 0
|
|
21788
21870
|
});
|
|
21789
21871
|
} else if (!stat.isDirectory()) {
|
|
@@ -21805,10 +21887,10 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21805
21887
|
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
21806
21888
|
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
21807
21889
|
return failure2("invalid_input", "candidate environment names must be unique and non-empty");
|
|
21808
|
-
const values = supplied.toSorted((left, right) =>
|
|
21890
|
+
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
21809
21891
|
if (values.some(({ value }) => value === null))
|
|
21810
21892
|
completeness = "uncertain";
|
|
21811
|
-
const headResult =
|
|
21893
|
+
const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
21812
21894
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
21813
21895
|
completeness = "uncertain";
|
|
21814
21896
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -22113,166 +22195,608 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
22113
22195
|
});
|
|
22114
22196
|
}
|
|
22115
22197
|
|
|
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) };
|
|
22198
|
+
// packages/workit-core/src/core/methods.ts
|
|
22199
|
+
var METHODS = {
|
|
22200
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
22201
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
22202
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
22203
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
22204
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
22205
|
+
"workit-debug": {},
|
|
22206
|
+
"workit-handoff": {},
|
|
22207
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
22208
|
+
};
|
|
22209
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
22210
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
22211
|
+
const keys = new Set([
|
|
22212
|
+
id,
|
|
22213
|
+
...definition.dimensions ?? [],
|
|
22214
|
+
...definition.ruleIds ?? []
|
|
22215
|
+
]);
|
|
22216
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
22217
|
+
if (id === "workit-review" && matches.length === 0)
|
|
22218
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
22219
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
22220
|
+
return {
|
|
22221
|
+
assurance: "unavailable",
|
|
22222
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
22223
|
+
};
|
|
22224
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
22225
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
22226
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
22227
|
+
};
|
|
22228
|
+
function selectMethods(policy, capabilities) {
|
|
22229
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
22230
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
22231
|
+
if (requirements.length === 0)
|
|
22232
|
+
return [];
|
|
22233
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
22234
|
+
const reasons = [
|
|
22235
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
22236
|
+
...capability.reasons
|
|
22237
|
+
];
|
|
22238
|
+
if (capability.assurance === "unavailable")
|
|
22239
|
+
reasons.unshift("required capability is unavailable");
|
|
22240
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
22241
|
+
});
|
|
22242
|
+
}
|
|
22243
|
+
|
|
22244
|
+
// packages/workit-core/src/core/task-context.ts
|
|
22245
|
+
function reconcileResume(view, observations = []) {
|
|
22246
|
+
if (observations.length > 0)
|
|
22247
|
+
return failure2("permission_denied", "worker observations require native host verification");
|
|
22248
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
22249
|
+
if (!candidate.ok)
|
|
22250
|
+
return candidate;
|
|
22251
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
22252
|
+
const blockers = [...view.task.progress.blockers];
|
|
22253
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
22254
|
+
blockers.push({
|
|
22255
|
+
reason: "worker state requires reconciliation",
|
|
22256
|
+
dependentAction: "resume",
|
|
22257
|
+
refs: []
|
|
22258
|
+
});
|
|
22259
|
+
return success(null, null, {
|
|
22260
|
+
candidate: candidate.data,
|
|
22261
|
+
staleEvidenceIds,
|
|
22262
|
+
workerUpdates: [],
|
|
22263
|
+
blockers,
|
|
22264
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
22265
|
+
});
|
|
22266
|
+
}
|
|
22267
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
22268
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
22269
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
22270
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
22271
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
22272
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
22273
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
22274
|
+
const separator = match.search(/[:=]/u);
|
|
22275
|
+
if (separator >= 0)
|
|
22276
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
22277
|
+
const space = match.search(/\s/u);
|
|
22278
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
22279
|
+
}).slice(0, limit);
|
|
22280
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
22281
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
22282
|
+
var compactDecisionChoice = (entry) => {
|
|
22283
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
22284
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
22285
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
22286
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
22287
|
+
};
|
|
22288
|
+
var compactReference = (ref) => {
|
|
22289
|
+
if (ref.kind === "file")
|
|
22290
|
+
return {
|
|
22291
|
+
kind: "file",
|
|
22292
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
22293
|
+
};
|
|
22294
|
+
if (ref.kind === "record")
|
|
22295
|
+
return {
|
|
22296
|
+
kind: "record",
|
|
22297
|
+
collection: ref.collection,
|
|
22298
|
+
id: ref.id
|
|
22299
|
+
};
|
|
22300
|
+
return { kind: ref.kind };
|
|
22301
|
+
};
|
|
22302
|
+
function compactTaskContext(view) {
|
|
22303
|
+
const decisions = view.task.decisions.slice().toSorted((left, right) => {
|
|
22304
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
22305
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
22306
|
+
if (leftAt !== rightAt)
|
|
22307
|
+
return leftAt > rightAt ? -1 : 1;
|
|
22308
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
22309
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
22310
|
+
id: entry.id,
|
|
22311
|
+
purpose: entry.data.purpose,
|
|
22312
|
+
status: entry.data.response,
|
|
22313
|
+
digest: entry.data.digest,
|
|
22314
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
22315
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
22316
|
+
}));
|
|
22317
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
22318
|
+
id: method.id,
|
|
22319
|
+
assurance: method.assurance,
|
|
22320
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
22321
|
+
})) : [];
|
|
22322
|
+
const gaps = Array.from(new Set([
|
|
22323
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
22324
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
22325
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
22326
|
+
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
22327
|
+
const context = {
|
|
22328
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
22329
|
+
status: view.task.status,
|
|
22330
|
+
decisions,
|
|
22331
|
+
methods,
|
|
22332
|
+
gaps,
|
|
22333
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
22334
|
+
};
|
|
22335
|
+
let encoded = JSON.stringify(context);
|
|
22336
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
22337
|
+
if (context.gaps.length)
|
|
22338
|
+
context.gaps.pop();
|
|
22339
|
+
else if (context.decisions.length)
|
|
22340
|
+
context.decisions.pop();
|
|
22341
|
+
else
|
|
22342
|
+
context.methods.pop();
|
|
22343
|
+
encoded = JSON.stringify(context);
|
|
22344
|
+
}
|
|
22345
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
22346
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
22347
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
22348
|
+
encoded = JSON.stringify(context);
|
|
22349
|
+
}
|
|
22350
|
+
return encoded;
|
|
22351
|
+
}
|
|
22352
|
+
var exportDigest = (bundle) => sha256({
|
|
22353
|
+
schemaVersion: bundle.schemaVersion,
|
|
22354
|
+
exportedAt: bundle.exportedAt,
|
|
22355
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
22356
|
+
task: bundle.task
|
|
22357
|
+
});
|
|
22358
|
+
// packages/workit-core/src/core/external-action.ts
|
|
22359
|
+
var chainStepSchema = union([
|
|
22360
|
+
string2().min(1),
|
|
22361
|
+
object2({ branch: string2().min(1) }).strict(),
|
|
22362
|
+
object2({ pr: literal(true) }).strict()
|
|
22363
|
+
]);
|
|
22364
|
+
var externalActionSchema = discriminatedUnion("operation", [
|
|
22365
|
+
object2({
|
|
22366
|
+
operation: literal("git.branch_setup"),
|
|
22367
|
+
payload: object2({
|
|
22368
|
+
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
22369
|
+
sdd_dir: string2().optional(),
|
|
22370
|
+
target_branch: string2().optional(),
|
|
22371
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
22372
|
+
cwd: string2().min(1).optional()
|
|
22373
|
+
}).strict()
|
|
22374
|
+
}).strict(),
|
|
22375
|
+
object2({
|
|
22376
|
+
operation: literal("git.commit"),
|
|
22377
|
+
payload: object2({
|
|
22378
|
+
message: string2().min(1).optional(),
|
|
22379
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
22380
|
+
plan_branch: string2().min(1).optional(),
|
|
22381
|
+
cwd: string2().min(1).optional()
|
|
22382
|
+
}).strict()
|
|
22383
|
+
}).strict(),
|
|
22384
|
+
object2({
|
|
22385
|
+
operation: literal("git.push"),
|
|
22386
|
+
payload: object2({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
22387
|
+
}).strict(),
|
|
22388
|
+
object2({
|
|
22389
|
+
operation: literal("hosting.pull_request"),
|
|
22390
|
+
payload: object2({
|
|
22391
|
+
title: string2().min(1),
|
|
22392
|
+
body: string2().optional(),
|
|
22393
|
+
draft: boolean2().optional(),
|
|
22394
|
+
target_branch: string2().optional(),
|
|
22395
|
+
babysit: boolean2().optional(),
|
|
22396
|
+
cwd: string2().min(1).optional()
|
|
22397
|
+
}).strict()
|
|
22398
|
+
}).strict(),
|
|
22399
|
+
object2({
|
|
22400
|
+
operation: literal("hosting.merge"),
|
|
22401
|
+
payload: object2({
|
|
22402
|
+
target_branch: string2().min(1).optional(),
|
|
22403
|
+
source_branch: string2().min(1).optional(),
|
|
22404
|
+
cwd: string2().min(1).optional()
|
|
22405
|
+
}).strict()
|
|
22406
|
+
}).strict(),
|
|
22407
|
+
object2({
|
|
22408
|
+
operation: literal("hosting.delete_branch"),
|
|
22409
|
+
payload: object2({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
22410
|
+
}).strict(),
|
|
22411
|
+
object2({
|
|
22412
|
+
operation: literal("youtrack.update"),
|
|
22413
|
+
payload: object2({
|
|
22414
|
+
issueId: string2().min(1),
|
|
22415
|
+
markdown: string2().min(1),
|
|
22416
|
+
minutes: number2().positive().optional()
|
|
22417
|
+
}).strict()
|
|
22418
|
+
}).strict(),
|
|
22419
|
+
object2({
|
|
22420
|
+
operation: literal("youtrack.time"),
|
|
22421
|
+
payload: object2({
|
|
22422
|
+
issueId: string2().min(1),
|
|
22423
|
+
minutes: number2().positive(),
|
|
22424
|
+
text: string2().optional(),
|
|
22425
|
+
dateMs: number2().finite().optional()
|
|
22426
|
+
}).strict()
|
|
22427
|
+
}).strict(),
|
|
22428
|
+
object2({
|
|
22429
|
+
operation: literal("youtrack.meeting"),
|
|
22430
|
+
payload: object2({
|
|
22431
|
+
issueId: string2().min(1),
|
|
22432
|
+
minutes: number2().positive(),
|
|
22433
|
+
text: string2().min(1)
|
|
22434
|
+
}).strict()
|
|
22435
|
+
}).strict(),
|
|
22436
|
+
object2({
|
|
22437
|
+
operation: literal("changelog.apply"),
|
|
22438
|
+
payload: object2({
|
|
22439
|
+
entries: union([
|
|
22440
|
+
record(string2(), array(string2())),
|
|
22441
|
+
array(object2({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
22442
|
+
]).optional(),
|
|
22443
|
+
path: string2().optional(),
|
|
22444
|
+
normalize_only: boolean2().optional()
|
|
22445
|
+
}).strict()
|
|
22446
|
+
}).strict(),
|
|
22447
|
+
object2({
|
|
22448
|
+
operation: literal("context.read"),
|
|
22449
|
+
payload: object2({
|
|
22450
|
+
kind: _enum([
|
|
22451
|
+
"git",
|
|
22452
|
+
"pr",
|
|
22453
|
+
"youtrack",
|
|
22454
|
+
"github_issue",
|
|
22455
|
+
"gitlab_issue",
|
|
22456
|
+
"changelog",
|
|
22457
|
+
"release",
|
|
22458
|
+
"affected"
|
|
22459
|
+
]),
|
|
22460
|
+
range: string2().optional(),
|
|
22461
|
+
issueId: string2().optional(),
|
|
22462
|
+
issueUrl: string2().optional(),
|
|
22463
|
+
issueRef: string2().optional(),
|
|
22464
|
+
mode: string2().optional(),
|
|
22465
|
+
specPath: string2().optional(),
|
|
22466
|
+
planPath: string2().optional(),
|
|
22467
|
+
cwd: string2().min(1).optional()
|
|
22468
|
+
}).strict()
|
|
22469
|
+
}).strict()
|
|
22470
|
+
]);
|
|
22471
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
22472
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
22473
|
+
|
|
22474
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
22475
|
+
var resolverInputSchema = object2({
|
|
22476
|
+
intent: intentSchema,
|
|
22477
|
+
assessment: assessmentSchema,
|
|
22478
|
+
constraints: array(constraintSchema),
|
|
22479
|
+
prior: object2({
|
|
22480
|
+
decisions: array(decisionSchema),
|
|
22481
|
+
findings: array(findingSchema),
|
|
22482
|
+
requirements: array(requirementSchema)
|
|
22483
|
+
}).strict(),
|
|
22484
|
+
preferences: object2({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
22485
|
+
}).strict();
|
|
22486
|
+
var compareCodeUnits3 = (left, right) => {
|
|
22487
|
+
const length = Math.min(left.length, right.length);
|
|
22488
|
+
for (let index = 0;index < length; index += 1) {
|
|
22489
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
22490
|
+
if (difference !== 0)
|
|
22491
|
+
return difference;
|
|
22492
|
+
}
|
|
22493
|
+
return left.length - right.length;
|
|
22145
22494
|
};
|
|
22146
|
-
|
|
22147
|
-
|
|
22148
|
-
|
|
22149
|
-
|
|
22150
|
-
|
|
22151
|
-
|
|
22152
|
-
|
|
22153
|
-
|
|
22154
|
-
|
|
22155
|
-
|
|
22156
|
-
|
|
22157
|
-
|
|
22158
|
-
|
|
22159
|
-
|
|
22160
|
-
|
|
22161
|
-
|
|
22162
|
-
|
|
22163
|
-
|
|
22164
|
-
|
|
22165
|
-
|
|
22166
|
-
|
|
22167
|
-
|
|
22168
|
-
|
|
22169
|
-
|
|
22170
|
-
|
|
22171
|
-
|
|
22172
|
-
|
|
22173
|
-
|
|
22174
|
-
|
|
22175
|
-
|
|
22495
|
+
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
22496
|
+
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
22497
|
+
var normalizedScope = (scope) => ({
|
|
22498
|
+
description: scope.description,
|
|
22499
|
+
paths: stableUnique(scope.paths),
|
|
22500
|
+
exclusions: stableUnique(scope.exclusions)
|
|
22501
|
+
});
|
|
22502
|
+
var normalizedRefList = (values) => stableList(values);
|
|
22503
|
+
var normalizeAssessment = (assessment) => ({
|
|
22504
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
22505
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
22506
|
+
name,
|
|
22507
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
22508
|
+
])),
|
|
22509
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
22510
|
+
...consequence,
|
|
22511
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
22512
|
+
}))),
|
|
22513
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
22514
|
+
...verification,
|
|
22515
|
+
scope: normalizedScope(verification.scope),
|
|
22516
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
22517
|
+
gaps: stableUnique(verification.gaps)
|
|
22518
|
+
})))
|
|
22519
|
+
});
|
|
22520
|
+
var normalizeConstraint = (constraint) => ({
|
|
22521
|
+
...constraint,
|
|
22522
|
+
source: constraint.source,
|
|
22523
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
22524
|
+
...requirement,
|
|
22525
|
+
scope: normalizedScope(requirement.scope),
|
|
22526
|
+
refs: normalizedRefList(requirement.refs)
|
|
22527
|
+
})))
|
|
22528
|
+
});
|
|
22529
|
+
var normalizeDecision = (decision) => ({
|
|
22530
|
+
...decision,
|
|
22531
|
+
binding: {
|
|
22532
|
+
...decision.binding,
|
|
22533
|
+
scope: normalizedScope(decision.binding.scope),
|
|
22534
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
22535
|
+
},
|
|
22536
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
22537
|
+
});
|
|
22538
|
+
var normalizeIntent = (intent) => ({
|
|
22539
|
+
...intent,
|
|
22540
|
+
scope: normalizedScope(intent.scope),
|
|
22541
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
22542
|
+
});
|
|
22543
|
+
var normalizePrior = (prior) => ({
|
|
22544
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
22545
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
22546
|
+
...finding,
|
|
22547
|
+
scope: normalizedScope(finding.scope),
|
|
22548
|
+
refs: normalizedRefList(finding.refs),
|
|
22549
|
+
resolution: finding.resolution ? {
|
|
22550
|
+
...finding.resolution,
|
|
22551
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
22552
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
22553
|
+
} : null
|
|
22554
|
+
}))),
|
|
22555
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
22556
|
+
...requirement,
|
|
22557
|
+
scope: normalizedScope(requirement.scope)
|
|
22558
|
+
})))
|
|
22559
|
+
});
|
|
22560
|
+
var invalidFields = (error) => ({
|
|
22561
|
+
fields: error.issues.map((issue) => ({
|
|
22562
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
22563
|
+
reason: issue.message
|
|
22564
|
+
}))
|
|
22565
|
+
});
|
|
22566
|
+
function normalizeResolverInput(input) {
|
|
22567
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
22568
|
+
if (!parsed.success)
|
|
22569
|
+
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
22570
|
+
const value = parsed.data;
|
|
22571
|
+
const signals = value.assessment.signals;
|
|
22572
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
22573
|
+
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
22574
|
+
fields: [
|
|
22575
|
+
{
|
|
22576
|
+
path: "assessment.signals",
|
|
22577
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
22578
|
+
}
|
|
22579
|
+
]
|
|
22176
22580
|
});
|
|
22177
|
-
|
|
22178
|
-
|
|
22179
|
-
|
|
22180
|
-
|
|
22181
|
-
|
|
22182
|
-
|
|
22183
|
-
|
|
22581
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
22582
|
+
if (new Set(ids).size !== ids.length)
|
|
22583
|
+
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
22584
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
22585
|
+
});
|
|
22586
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
22587
|
+
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
22588
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
22589
|
+
});
|
|
22590
|
+
const normalized = {
|
|
22591
|
+
intent: normalizeIntent(value.intent),
|
|
22592
|
+
assessment: normalizeAssessment(value.assessment),
|
|
22593
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
22594
|
+
prior: normalizePrior(value.prior),
|
|
22595
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
22596
|
+
policyVersion: POLICY_VERSION
|
|
22597
|
+
};
|
|
22598
|
+
return success(null, null, normalized);
|
|
22184
22599
|
}
|
|
22185
|
-
var
|
|
22186
|
-
|
|
22187
|
-
|
|
22188
|
-
|
|
22189
|
-
|
|
22190
|
-
|
|
22191
|
-
|
|
22192
|
-
|
|
22193
|
-
|
|
22194
|
-
|
|
22195
|
-
|
|
22196
|
-
|
|
22197
|
-
}
|
|
22198
|
-
|
|
22199
|
-
|
|
22200
|
-
|
|
22201
|
-
const
|
|
22202
|
-
|
|
22203
|
-
|
|
22204
|
-
|
|
22205
|
-
|
|
22206
|
-
|
|
22207
|
-
|
|
22208
|
-
|
|
22209
|
-
|
|
22210
|
-
|
|
22211
|
-
|
|
22212
|
-
|
|
22213
|
-
|
|
22214
|
-
|
|
22215
|
-
|
|
22216
|
-
|
|
22217
|
-
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
|
|
22223
|
-
|
|
22224
|
-
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
|
|
22231
|
-
|
|
22232
|
-
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22600
|
+
var requirement = (spec) => ({
|
|
22601
|
+
...spec,
|
|
22602
|
+
id: requirementId({
|
|
22603
|
+
ruleId: spec.ruleId,
|
|
22604
|
+
scope: spec.scope,
|
|
22605
|
+
satisfaction: spec.satisfaction,
|
|
22606
|
+
before: spec.before,
|
|
22607
|
+
dependentAction: spec.dependentAction
|
|
22608
|
+
})
|
|
22609
|
+
});
|
|
22610
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
22611
|
+
function resolveRequirements(input) {
|
|
22612
|
+
const { signals } = input.assessment;
|
|
22613
|
+
const scope = input.intent.scope;
|
|
22614
|
+
const requirements = [];
|
|
22615
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
22616
|
+
const hasUnknownFact = [
|
|
22617
|
+
...input.assessment.facts,
|
|
22618
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
22619
|
+
].some((fact) => fact.basis === "unknown");
|
|
22620
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
22621
|
+
requirements.push(requirement({
|
|
22622
|
+
ruleId: "consequential-unknown",
|
|
22623
|
+
dimension: "investigation",
|
|
22624
|
+
scope,
|
|
22625
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
22626
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
22627
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
22628
|
+
].join(", ")}`,
|
|
22629
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
22630
|
+
before: "dependent_action",
|
|
22631
|
+
dependentAction: null,
|
|
22632
|
+
acceptanceAllowed: false
|
|
22633
|
+
}));
|
|
22634
|
+
if (signals.productChoiceOpen.value === true)
|
|
22635
|
+
requirements.push(requirement({
|
|
22636
|
+
ruleId: "product-decision",
|
|
22637
|
+
dimension: "decisions",
|
|
22638
|
+
scope,
|
|
22639
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
22640
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
22641
|
+
before: "dependent_action",
|
|
22642
|
+
dependentAction: null,
|
|
22643
|
+
acceptanceAllowed: false
|
|
22644
|
+
}));
|
|
22645
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
22646
|
+
if (signals.behaviorChange.value === true)
|
|
22647
|
+
requirements.push(requirement({
|
|
22648
|
+
ruleId: "behavioral-verification",
|
|
22649
|
+
dimension: "testing",
|
|
22650
|
+
scope,
|
|
22651
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
22652
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
22653
|
+
before: "close",
|
|
22654
|
+
dependentAction: null,
|
|
22655
|
+
acceptanceAllowed: false
|
|
22656
|
+
}), requirement({
|
|
22657
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
22658
|
+
dimension: "review",
|
|
22659
|
+
scope,
|
|
22660
|
+
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.",
|
|
22661
|
+
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.",
|
|
22662
|
+
before: "close",
|
|
22663
|
+
dependentAction: null,
|
|
22664
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
22665
|
+
}));
|
|
22666
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
22667
|
+
requirements.push(requirement({
|
|
22668
|
+
ruleId: "mechanical-existing-checks",
|
|
22669
|
+
dimension: "verification",
|
|
22670
|
+
scope,
|
|
22671
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
22672
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
22673
|
+
before: "close",
|
|
22674
|
+
dependentAction: null,
|
|
22675
|
+
acceptanceAllowed: true
|
|
22676
|
+
}), requirement({
|
|
22677
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
22678
|
+
dimension: "review",
|
|
22679
|
+
scope,
|
|
22680
|
+
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.",
|
|
22681
|
+
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.",
|
|
22682
|
+
before: "close",
|
|
22683
|
+
dependentAction: null,
|
|
22684
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
22685
|
+
}));
|
|
22686
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
22687
|
+
requirements.push(requirement({
|
|
22688
|
+
ruleId: "pre-pr-cleanup",
|
|
22689
|
+
dimension: "verification",
|
|
22690
|
+
scope,
|
|
22691
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
22692
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
22693
|
+
before: "dependent_action",
|
|
22694
|
+
dependentAction: "hosting.pull_request",
|
|
22695
|
+
acceptanceAllowed: true
|
|
22696
|
+
}));
|
|
22697
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
22698
|
+
requirements.push(requirement({
|
|
22699
|
+
ruleId: "durable-spec",
|
|
22700
|
+
dimension: "artifacts",
|
|
22701
|
+
scope,
|
|
22702
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
22703
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
22704
|
+
before: "write",
|
|
22705
|
+
dependentAction: null,
|
|
22706
|
+
acceptanceAllowed: true
|
|
22707
|
+
}));
|
|
22708
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
22709
|
+
requirements.push(requirement({
|
|
22710
|
+
ruleId: "coordination-plan",
|
|
22711
|
+
dimension: "continuity",
|
|
22712
|
+
scope,
|
|
22713
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
22714
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
22715
|
+
before: "dependent_action",
|
|
22716
|
+
dependentAction: null,
|
|
22717
|
+
acceptanceAllowed: true
|
|
22718
|
+
}));
|
|
22719
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
22720
|
+
requirements.push(requirement({
|
|
22721
|
+
ruleId: "helper-usefulness",
|
|
22722
|
+
dimension: "delegation",
|
|
22723
|
+
scope,
|
|
22724
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
22725
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
22726
|
+
before: "dependent_action",
|
|
22727
|
+
dependentAction: null,
|
|
22728
|
+
acceptanceAllowed: true
|
|
22729
|
+
}));
|
|
22730
|
+
for (const constraint of input.constraints)
|
|
22731
|
+
for (const obligation of constraint.requires) {
|
|
22732
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
22733
|
+
requirements.push(requirement({
|
|
22734
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
22735
|
+
kind: obligation.kind,
|
|
22736
|
+
scope: normalizedScope(obligation.scope),
|
|
22737
|
+
refs: normalizedRefList(obligation.refs),
|
|
22738
|
+
method: obligation.method,
|
|
22739
|
+
before: obligation.before,
|
|
22740
|
+
dependentAction: obligation.dependentAction
|
|
22741
|
+
})}`,
|
|
22742
|
+
dimension,
|
|
22743
|
+
scope: obligation.scope,
|
|
22744
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
22745
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
22746
|
+
before: obligation.before,
|
|
22747
|
+
dependentAction: obligation.dependentAction,
|
|
22748
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
22749
|
+
}));
|
|
22750
|
+
}
|
|
22751
|
+
const seen = new Set;
|
|
22752
|
+
return requirements.filter((item) => {
|
|
22753
|
+
if (seen.has(item.id))
|
|
22754
|
+
return false;
|
|
22755
|
+
seen.add(item.id);
|
|
22756
|
+
return true;
|
|
22757
|
+
});
|
|
22758
|
+
}
|
|
22759
|
+
function resolvePolicy(input) {
|
|
22760
|
+
const normalized = normalizeResolverInput(input);
|
|
22761
|
+
if (!normalized.ok)
|
|
22762
|
+
return normalized;
|
|
22763
|
+
const requirements = resolveRequirements(normalized.data);
|
|
22764
|
+
const policy = {
|
|
22765
|
+
policyVersion: POLICY_VERSION,
|
|
22766
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
22767
|
+
requirements
|
|
22252
22768
|
};
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22769
|
+
const parsed = policySchema.safeParse(policy);
|
|
22770
|
+
if (!parsed.success)
|
|
22771
|
+
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
22772
|
+
return success(null, null, parsed.data);
|
|
22773
|
+
}
|
|
22774
|
+
function diffPolicy(previous, next, reason, now) {
|
|
22775
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
22776
|
+
throw new TypeError("previous policy is invalid");
|
|
22777
|
+
if (!policySchema.safeParse(next).success)
|
|
22778
|
+
throw new TypeError("next policy is invalid");
|
|
22779
|
+
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
22780
|
+
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
22781
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits3);
|
|
22782
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits3);
|
|
22783
|
+
if (added.length === 0 && retired.length === 0)
|
|
22784
|
+
return null;
|
|
22785
|
+
const normalizedReason = reason.trim();
|
|
22786
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
22787
|
+
const change = {
|
|
22788
|
+
recordedAt: now,
|
|
22789
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
22790
|
+
toInputDigest: next.inputDigest,
|
|
22791
|
+
added,
|
|
22792
|
+
retired,
|
|
22793
|
+
reason: changeReason
|
|
22794
|
+
};
|
|
22795
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
22796
|
+
if (!parsed.success)
|
|
22797
|
+
throw new TypeError("policy change is invalid");
|
|
22798
|
+
return parsed.data;
|
|
22269
22799
|
}
|
|
22270
|
-
var exportDigest = (bundle) => sha256({
|
|
22271
|
-
schemaVersion: bundle.schemaVersion,
|
|
22272
|
-
exportedAt: bundle.exportedAt,
|
|
22273
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
22274
|
-
task: bundle.task
|
|
22275
|
-
});
|
|
22276
22800
|
|
|
22277
22801
|
// packages/workit-core/src/core/task-engine.ts
|
|
22278
22802
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -22445,6 +22969,29 @@ var applyWorkerLifecycle = (input) => {
|
|
|
22445
22969
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
|
|
22446
22970
|
};
|
|
22447
22971
|
var refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || bindingCovers(scope, { description: "", paths: [ref.path], exclusions: [] }));
|
|
22972
|
+
var REVISION_RETRY_ATTEMPTS = 8;
|
|
22973
|
+
var retryPause = new Int32Array(new SharedArrayBuffer(4));
|
|
22974
|
+
var filledRevisions = (request) => {
|
|
22975
|
+
const value = typeof request === "object" && request !== null ? request : {};
|
|
22976
|
+
return {
|
|
22977
|
+
task: value.expectedRevision === undefined,
|
|
22978
|
+
workspace: value.expectedWorkspaceRevision === undefined
|
|
22979
|
+
};
|
|
22980
|
+
};
|
|
22981
|
+
var filledConflict = (result, filled) => !result.ok && result.code === "revision_conflict" && (("actualRevision" in result.details) && filled.task || ("actualWorkspaceRevision" in result.details) && filled.workspace);
|
|
22982
|
+
var retryFilledRevisions = (filled, run) => {
|
|
22983
|
+
if (!filled.task && !filled.workspace)
|
|
22984
|
+
return run();
|
|
22985
|
+
let result = run();
|
|
22986
|
+
const deadline = Date.now() + defaultLockTimeout();
|
|
22987
|
+
for (let attempt = 1;filledConflict(result, filled); attempt += 1) {
|
|
22988
|
+
if (attempt >= REVISION_RETRY_ATTEMPTS || Date.now() >= deadline)
|
|
22989
|
+
return failure2("busy", "records kept changing under concurrent writers; retry the call");
|
|
22990
|
+
Atomics.wait(retryPause, 0, 0, Math.floor(Math.random() * 4 * attempt) + 1);
|
|
22991
|
+
result = run();
|
|
22992
|
+
}
|
|
22993
|
+
return result;
|
|
22994
|
+
};
|
|
22448
22995
|
var trustedNow = (context) => typeof context.now === "function" ? context.now() : context.now;
|
|
22449
22996
|
var mapRef = (ref, ids) => {
|
|
22450
22997
|
if (ref.kind !== "record")
|
|
@@ -22692,6 +23239,9 @@ class WorkitCore {
|
|
|
22692
23239
|
if (input.expectedWorkspaceRevision === undefined)
|
|
22693
23240
|
input.expectedWorkspaceRevision = workspace ? workspace.revision : null;
|
|
22694
23241
|
}
|
|
23242
|
+
retryOmittedRevisions(request, run) {
|
|
23243
|
+
return retryFilledRevisions(filledRevisions(request), run);
|
|
23244
|
+
}
|
|
22695
23245
|
helperEntry(task, requireSession = false, requireActive = false) {
|
|
22696
23246
|
const workerId = this.context.workerId ?? null;
|
|
22697
23247
|
if (workerId === null)
|
|
@@ -22723,6 +23273,9 @@ class WorkitCore {
|
|
|
22723
23273
|
return null;
|
|
22724
23274
|
}
|
|
22725
23275
|
state(request) {
|
|
23276
|
+
return request?.action === "import" ? this.retryOmittedRevisions(request, () => this.stateOnce(request)) : this.stateOnce(request);
|
|
23277
|
+
}
|
|
23278
|
+
stateOnce(request) {
|
|
22726
23279
|
const root = this.contextRootError();
|
|
22727
23280
|
if (!root.ok)
|
|
22728
23281
|
return root;
|
|
@@ -22824,6 +23377,9 @@ class WorkitCore {
|
|
|
22824
23377
|
});
|
|
22825
23378
|
}
|
|
22826
23379
|
task(request) {
|
|
23380
|
+
return this.retryOmittedRevisions(request, () => this.taskOnce(request));
|
|
23381
|
+
}
|
|
23382
|
+
taskOnce(request) {
|
|
22827
23383
|
const root = this.contextRootError();
|
|
22828
23384
|
if (!root.ok)
|
|
22829
23385
|
return root;
|
|
@@ -22923,6 +23479,9 @@ class WorkitCore {
|
|
|
22923
23479
|
}
|
|
22924
23480
|
}
|
|
22925
23481
|
policy(request) {
|
|
23482
|
+
return this.retryOmittedRevisions(request, () => this.policyOnce(request));
|
|
23483
|
+
}
|
|
23484
|
+
policyOnce(request) {
|
|
22926
23485
|
const root = this.contextRootError();
|
|
22927
23486
|
if (!root.ok)
|
|
22928
23487
|
return root;
|
|
@@ -22969,6 +23528,9 @@ class WorkitCore {
|
|
|
22969
23528
|
return success(changed.data.revision, null, changed.data.policy);
|
|
22970
23529
|
}
|
|
22971
23530
|
evidence(request) {
|
|
23531
|
+
return this.retryOmittedRevisions(request, () => this.evidenceOnce(request));
|
|
23532
|
+
}
|
|
23533
|
+
evidenceOnce(request) {
|
|
22972
23534
|
const root = this.contextRootError();
|
|
22973
23535
|
if (!root.ok)
|
|
22974
23536
|
return root;
|
|
@@ -23053,6 +23615,23 @@ class WorkitCore {
|
|
|
23053
23615
|
observeStandingDecision(request) {
|
|
23054
23616
|
return this.recordDecision(request, undefined, true, true);
|
|
23055
23617
|
}
|
|
23618
|
+
commitTask(taskId, expected, filled, recheck, update) {
|
|
23619
|
+
let revision = expected;
|
|
23620
|
+
let first = true;
|
|
23621
|
+
return retryFilledRevisions({ task: filled, workspace: false }, () => {
|
|
23622
|
+
if (!first) {
|
|
23623
|
+
const fresh = this.store.readTask(taskId);
|
|
23624
|
+
if (!fresh.ok)
|
|
23625
|
+
return fresh;
|
|
23626
|
+
const checked = recheck(fresh.data);
|
|
23627
|
+
if (!checked.ok)
|
|
23628
|
+
return checked;
|
|
23629
|
+
revision = fresh.data.revision;
|
|
23630
|
+
}
|
|
23631
|
+
first = false;
|
|
23632
|
+
return this.store.mutateTask(taskId, revision, update, trustedNow(this.context));
|
|
23633
|
+
});
|
|
23634
|
+
}
|
|
23056
23635
|
recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
|
|
23057
23636
|
const root = this.contextRootError();
|
|
23058
23637
|
if (!root.ok)
|
|
@@ -23077,6 +23656,7 @@ class WorkitCore {
|
|
|
23077
23656
|
return task;
|
|
23078
23657
|
if ((this.context.workerId ?? null) !== null)
|
|
23079
23658
|
return failure2("permission_denied", "helpers cannot record or revoke decisions");
|
|
23659
|
+
const filled = input.expectedRevision === undefined;
|
|
23080
23660
|
this.fillRevisions(input, task.data);
|
|
23081
23661
|
if (input.action === "record") {
|
|
23082
23662
|
if (task.data.status === "closed")
|
|
@@ -23125,7 +23705,20 @@ class WorkitCore {
|
|
|
23125
23705
|
const nativeProvenance = native?.ok ? retireNativeAuthority(native.data) : null;
|
|
23126
23706
|
if (native?.ok && !nativeProvenance)
|
|
23127
23707
|
return failure2("permission_denied", "native decision authority was retired");
|
|
23128
|
-
|
|
23708
|
+
let standingProvenance = standingApproval?.ok === true ? standingApproval.data : null;
|
|
23709
|
+
const recheck = (fresh) => {
|
|
23710
|
+
if (fresh.status === "closed")
|
|
23711
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
23712
|
+
if (!standingProvenance)
|
|
23713
|
+
return success(null, null, null);
|
|
23714
|
+
const again = verifyStandingApproval(this.store.root, fresh, this.context.caller, input.binding);
|
|
23715
|
+
if (again.ok)
|
|
23716
|
+
standingProvenance = again.data;
|
|
23717
|
+
return again;
|
|
23718
|
+
};
|
|
23719
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, recheck, (current, mutation) => {
|
|
23720
|
+
if (current.status === "closed")
|
|
23721
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
23129
23722
|
const latestContent = verifyDecisionContent(this.store, input.binding);
|
|
23130
23723
|
if (!latestContent.ok)
|
|
23131
23724
|
return latestContent;
|
|
@@ -23137,14 +23730,14 @@ class WorkitCore {
|
|
|
23137
23730
|
const entry = {
|
|
23138
23731
|
id: newId(),
|
|
23139
23732
|
recordedAt: mutation.now,
|
|
23140
|
-
provenance:
|
|
23733
|
+
provenance: standingProvenance ?? nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
23141
23734
|
data
|
|
23142
23735
|
};
|
|
23143
23736
|
return success(mutation.revision, null, {
|
|
23144
23737
|
...current,
|
|
23145
23738
|
decisions: [...current.decisions, entry]
|
|
23146
23739
|
});
|
|
23147
|
-
}
|
|
23740
|
+
});
|
|
23148
23741
|
if (!changed.ok)
|
|
23149
23742
|
return changed;
|
|
23150
23743
|
return success(changed.data.revision, null, changed.data.decisions.at(-1));
|
|
@@ -23158,7 +23751,7 @@ class WorkitCore {
|
|
|
23158
23751
|
return failure2("invalid_transition", "decision is already revoked");
|
|
23159
23752
|
if (decision.data.consumption)
|
|
23160
23753
|
return failure2("permission_denied", "consumed decision cannot be revoked");
|
|
23161
|
-
const changed = this.
|
|
23754
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, () => success(null, null, null), (current, mutation) => {
|
|
23162
23755
|
const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
23163
23756
|
if (!entry)
|
|
23164
23757
|
return failure2("not_found", "decision not found");
|
|
@@ -23175,13 +23768,16 @@ class WorkitCore {
|
|
|
23175
23768
|
...current,
|
|
23176
23769
|
decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? updated : candidate)
|
|
23177
23770
|
});
|
|
23178
|
-
}
|
|
23771
|
+
});
|
|
23179
23772
|
if (!changed.ok)
|
|
23180
23773
|
return changed;
|
|
23181
23774
|
const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
23182
23775
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "decision disappeared");
|
|
23183
23776
|
}
|
|
23184
23777
|
finding(request) {
|
|
23778
|
+
return this.retryOmittedRevisions(request, () => this.findingOnce(request));
|
|
23779
|
+
}
|
|
23780
|
+
findingOnce(request) {
|
|
23185
23781
|
const root = this.contextRootError();
|
|
23186
23782
|
if (!root.ok)
|
|
23187
23783
|
return root;
|
|
@@ -23305,6 +23901,9 @@ class WorkitCore {
|
|
|
23305
23901
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "finding disappeared");
|
|
23306
23902
|
}
|
|
23307
23903
|
worker(request) {
|
|
23904
|
+
return this.retryOmittedRevisions(request, () => this.workerOnce(request));
|
|
23905
|
+
}
|
|
23906
|
+
workerOnce(request) {
|
|
23308
23907
|
const root = this.contextRootError();
|
|
23309
23908
|
if (!root.ok)
|
|
23310
23909
|
return root;
|
|
@@ -23652,6 +24251,9 @@ class WorkitCore {
|
|
|
23652
24251
|
return applied;
|
|
23653
24252
|
}
|
|
23654
24253
|
writer(request) {
|
|
24254
|
+
return this.retryOmittedRevisions(request, () => this.writerOnce(request));
|
|
24255
|
+
}
|
|
24256
|
+
writerOnce(request) {
|
|
23655
24257
|
const root = this.contextRootError();
|
|
23656
24258
|
if (!root.ok)
|
|
23657
24259
|
return root;
|
|
@@ -24254,122 +24856,6 @@ class WorkitCore {
|
|
|
24254
24856
|
return this.summary(changed.data.task);
|
|
24255
24857
|
}
|
|
24256
24858
|
}
|
|
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
24859
|
// packages/workit-core/src/core.ts
|
|
24374
24860
|
var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
|
|
24375
24861
|
function gitRevisionParts(value) {
|
|
@@ -24413,7 +24899,7 @@ function resolveInside(root, candidate) {
|
|
|
24413
24899
|
}
|
|
24414
24900
|
function run2(root, executable, args, env = {}) {
|
|
24415
24901
|
const cwd = realpathSync7(root);
|
|
24416
|
-
const result =
|
|
24902
|
+
const result = spawnSync4(executable, args, {
|
|
24417
24903
|
cwd,
|
|
24418
24904
|
encoding: "utf8",
|
|
24419
24905
|
env: { ...process.env, ...env }
|
|
@@ -24484,9 +24970,9 @@ var applyPatterns = (value) => {
|
|
|
24484
24970
|
var redactSecrets = (value) => applyPatterns(value);
|
|
24485
24971
|
|
|
24486
24972
|
// packages/workit-core/src/core/pr-create.ts
|
|
24487
|
-
import
|
|
24973
|
+
import fs9 from "node:fs";
|
|
24488
24974
|
import path12 from "node:path";
|
|
24489
|
-
import { spawnSync as
|
|
24975
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24490
24976
|
function parseGhIssue(value) {
|
|
24491
24977
|
const m = /issues\/(\d+)/.exec(value);
|
|
24492
24978
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
@@ -24499,7 +24985,7 @@ function whichOnPath(tool) {
|
|
|
24499
24985
|
for (const name of names) {
|
|
24500
24986
|
const candidate = path12.join(dir, name);
|
|
24501
24987
|
try {
|
|
24502
|
-
|
|
24988
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
24503
24989
|
return candidate;
|
|
24504
24990
|
} catch {}
|
|
24505
24991
|
}
|
|
@@ -24508,7 +24994,7 @@ function whichOnPath(tool) {
|
|
|
24508
24994
|
}
|
|
24509
24995
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
24510
24996
|
var pushRemote = (cwd) => {
|
|
24511
|
-
const result =
|
|
24997
|
+
const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
24512
24998
|
cwd,
|
|
24513
24999
|
encoding: "utf8"
|
|
24514
25000
|
});
|
|
@@ -24519,7 +25005,7 @@ var pushRemote = (cwd) => {
|
|
|
24519
25005
|
};
|
|
24520
25006
|
|
|
24521
25007
|
// packages/workit-core/src/core/repo-context.ts
|
|
24522
|
-
import { spawnSync as
|
|
25008
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
24523
25009
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
24524
25010
|
import path13 from "node:path";
|
|
24525
25011
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
@@ -24548,7 +25034,7 @@ var invalidRange = (cwd) => ({
|
|
|
24548
25034
|
cwd
|
|
24549
25035
|
});
|
|
24550
25036
|
var runGit = (cwd, args) => {
|
|
24551
|
-
const result =
|
|
25037
|
+
const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
|
|
24552
25038
|
return {
|
|
24553
25039
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24554
25040
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24721,7 +25207,7 @@ var documentationFiles = (cwd) => {
|
|
|
24721
25207
|
walk(cwd, 1);
|
|
24722
25208
|
return matches.toSorted().slice(0, 200);
|
|
24723
25209
|
};
|
|
24724
|
-
var
|
|
25210
|
+
var readTrimmed2 = (file, maxLines) => {
|
|
24725
25211
|
if (!existsSync6(file))
|
|
24726
25212
|
return "";
|
|
24727
25213
|
const lines = readFileSync7(file, "utf8").split(`
|
|
@@ -24788,7 +25274,7 @@ ${status.stderr}` : "");
|
|
|
24788
25274
|
stdout += `template_path: ${template}
|
|
24789
25275
|
|
|
24790
25276
|
`;
|
|
24791
|
-
stdout +=
|
|
25277
|
+
stdout += readTrimmed2(path13.join(cwd, template), 220);
|
|
24792
25278
|
} else {
|
|
24793
25279
|
stdout += `template_path: none
|
|
24794
25280
|
|
|
@@ -24860,7 +25346,7 @@ function changelogContext(root, range) {
|
|
|
24860
25346
|
stdout += printSection("Existing CHANGELOG.md");
|
|
24861
25347
|
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
24862
25348
|
if (existsSync6(changelogPath)) {
|
|
24863
|
-
stdout +=
|
|
25349
|
+
stdout += readTrimmed2(changelogPath, 260) + `
|
|
24864
25350
|
`;
|
|
24865
25351
|
} else {
|
|
24866
25352
|
stdout += `CHANGELOG.md not found.
|
|
@@ -24902,7 +25388,7 @@ function docsRefreshContext(root, range) {
|
|
|
24902
25388
|
stdout += printSection("README Preview");
|
|
24903
25389
|
const readme = path13.join(cwd, "README.md");
|
|
24904
25390
|
if (existsSync6(readme)) {
|
|
24905
|
-
stdout +=
|
|
25391
|
+
stdout += readTrimmed2(readme, 220) + `
|
|
24906
25392
|
`;
|
|
24907
25393
|
} else {
|
|
24908
25394
|
stdout += `README.md not found.
|
|
@@ -24989,7 +25475,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
24989
25475
|
}
|
|
24990
25476
|
|
|
24991
25477
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
24992
|
-
import { spawnSync as
|
|
25478
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
24993
25479
|
var repoPathFromRemote = (remote) => {
|
|
24994
25480
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
24995
25481
|
if (!rest)
|
|
@@ -25010,7 +25496,7 @@ var defaultCli = (bin, available) => {
|
|
|
25010
25496
|
if (!available)
|
|
25011
25497
|
return null;
|
|
25012
25498
|
return (args, root) => {
|
|
25013
|
-
const out =
|
|
25499
|
+
const out = spawnSync7(bin, args, {
|
|
25014
25500
|
cwd: root,
|
|
25015
25501
|
encoding: "utf8",
|
|
25016
25502
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25088,7 +25574,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25088
25574
|
};
|
|
25089
25575
|
|
|
25090
25576
|
// packages/workit-core/src/core/youtrack.ts
|
|
25091
|
-
import
|
|
25577
|
+
import fs10 from "node:fs";
|
|
25092
25578
|
import path14 from "node:path";
|
|
25093
25579
|
|
|
25094
25580
|
// packages/workit-core/src/core/templates.ts
|
|
@@ -25107,17 +25593,17 @@ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? p
|
|
|
25107
25593
|
var youTrackTokenModeOk = (p) => {
|
|
25108
25594
|
if (process.platform === "win32")
|
|
25109
25595
|
return true;
|
|
25110
|
-
const mode =
|
|
25596
|
+
const mode = fs10.statSync(p).mode & 511;
|
|
25111
25597
|
return mode === 384;
|
|
25112
25598
|
};
|
|
25113
25599
|
function readYouTrackConfig(required) {
|
|
25114
25600
|
const cfgPath = youTrackConfigPath();
|
|
25115
|
-
if (!
|
|
25601
|
+
if (!fs10.existsSync(cfgPath)) {
|
|
25116
25602
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25117
25603
|
}
|
|
25118
25604
|
let parsed;
|
|
25119
25605
|
try {
|
|
25120
|
-
parsed = JSON.parse(
|
|
25606
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25121
25607
|
} catch {
|
|
25122
25608
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25123
25609
|
}
|
|
@@ -25128,11 +25614,11 @@ function readYouTrackConfig(required) {
|
|
|
25128
25614
|
}
|
|
25129
25615
|
var readYouTrackContextConfig = () => {
|
|
25130
25616
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25131
|
-
if (!
|
|
25617
|
+
if (!fs10.existsSync(cfgPath))
|
|
25132
25618
|
return { data: {} };
|
|
25133
25619
|
let parsed;
|
|
25134
25620
|
try {
|
|
25135
|
-
parsed = JSON.parse(
|
|
25621
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25136
25622
|
} catch {
|
|
25137
25623
|
return { error: "YouTrack configuration is unavailable" };
|
|
25138
25624
|
}
|
|
@@ -25172,11 +25658,11 @@ var youTrackToken = () => {
|
|
|
25172
25658
|
return loaded;
|
|
25173
25659
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25174
25660
|
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
25175
|
-
if (!tokenPath || !
|
|
25661
|
+
if (!tokenPath || !fs10.existsSync(tokenPath))
|
|
25176
25662
|
return { error: "missing youtrack.token" };
|
|
25177
25663
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25178
25664
|
return { error: "youtrack.token mode must be 0600" };
|
|
25179
|
-
const token =
|
|
25665
|
+
const token = fs10.readFileSync(tokenPath, "utf8").trim();
|
|
25180
25666
|
if (!token)
|
|
25181
25667
|
return { error: "empty token file" };
|
|
25182
25668
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25241,9 +25727,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25241
25727
|
} catch {
|
|
25242
25728
|
continue;
|
|
25243
25729
|
}
|
|
25244
|
-
if (!
|
|
25730
|
+
if (!fs10.existsSync(full))
|
|
25245
25731
|
continue;
|
|
25246
|
-
const text =
|
|
25732
|
+
const text = fs10.readFileSync(full, "utf8");
|
|
25247
25733
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25248
25734
|
if (m)
|
|
25249
25735
|
return m[1];
|
|
@@ -25804,72 +26290,168 @@ var main = async () => {
|
|
|
25804
26290
|
var entryPath = process.argv[1] ? path16.resolve(process.argv[1]) : "";
|
|
25805
26291
|
if (entryPath === path16.resolve(fileURLToPath3(import.meta.url)))
|
|
25806
26292
|
await main();
|
|
26293
|
+
// packages/workit-core/src/hooks/descriptor.ts
|
|
26294
|
+
var support = (descriptor, axis) => {
|
|
26295
|
+
if (axis.startsWith("event:"))
|
|
26296
|
+
return descriptor.events[axis.slice("event:".length)].support;
|
|
26297
|
+
if (axis === "shellPolicy.deny")
|
|
26298
|
+
return descriptor.shellPolicy.deny;
|
|
26299
|
+
if (axis === "stopControl" || axis === "shellAvailable")
|
|
26300
|
+
return descriptor[axis];
|
|
26301
|
+
const [group, key] = axis.split(".");
|
|
26302
|
+
return descriptor[group][key];
|
|
26303
|
+
};
|
|
26304
|
+
var usable = (value) => value === "native" || value === "partial";
|
|
26305
|
+
var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map((rule) => {
|
|
26306
|
+
const levels = rule.requires.map((axis) => support(descriptor, axis));
|
|
26307
|
+
const available = rule.assurance !== "unavailable" && levels.every(usable) && (rule.observed ?? []).every((flag) => observed[flag] === true);
|
|
26308
|
+
const assurance = !available ? "unavailable" : rule.assurance === "enforced" && (levels.length === 0 || levels.some((level) => level !== "native")) ? "agent_guided" : rule.assurance;
|
|
26309
|
+
return {
|
|
26310
|
+
name: rule.name,
|
|
26311
|
+
surface: rule.surface,
|
|
26312
|
+
assurance,
|
|
26313
|
+
reason: assurance === "unavailable" ? rule.unavailableReason ?? rule.reason : rule.reason,
|
|
26314
|
+
refs: rule.refs.map((handle) => ({ kind: "host", host: descriptor.host, handle }))
|
|
26315
|
+
};
|
|
26316
|
+
});
|
|
26317
|
+
// packages/workit-core/src/hooks/context.ts
|
|
26318
|
+
var cache = new Map;
|
|
26319
|
+
// packages/workit-core/src/hooks/handle.ts
|
|
26320
|
+
var offered = new Set;
|
|
26321
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
26322
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
25807
26323
|
|
|
25808
|
-
// packages/workit-
|
|
25809
|
-
var
|
|
25810
|
-
var
|
|
25811
|
-
|
|
25812
|
-
|
|
26324
|
+
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
26325
|
+
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
26326
|
+
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
26327
|
+
var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
|
|
26328
|
+
|
|
26329
|
+
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
26330
|
+
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
26331
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
26332
|
+
|
|
26333
|
+
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
26334
|
+
var undocumented = { support: "undocumented", native: null };
|
|
26335
|
+
var none = { support: "none", native: null };
|
|
26336
|
+
var CURSOR_DESCRIPTOR = {
|
|
26337
|
+
host: "cursor",
|
|
26338
|
+
label: "Cursor",
|
|
26339
|
+
verifiedAgainst: "Cursor hooks docs; payloads in test/fixtures/hooks/cursor",
|
|
26340
|
+
docs: ["https://cursor.com/docs/agent/hooks"],
|
|
26341
|
+
transport: "hook-process",
|
|
26342
|
+
events: {
|
|
26343
|
+
"session.start": { support: "native", native: "sessionStart" },
|
|
26344
|
+
"context.turn": none,
|
|
26345
|
+
"shell.pre": { support: "native", native: "beforeShellExecution" },
|
|
26346
|
+
"tool.pre": { support: "native", native: "preToolUse" },
|
|
26347
|
+
"shell.post": undocumented,
|
|
26348
|
+
"subagent.start": { support: "native", native: "subagentStart" },
|
|
26349
|
+
"subagent.stop": { support: "partial", native: "subagentStop" },
|
|
26350
|
+
"prompt.submit": undocumented,
|
|
26351
|
+
"compact.pre": { support: "partial", native: "preCompact" },
|
|
26352
|
+
stop: undocumented
|
|
26353
|
+
},
|
|
26354
|
+
shellPolicy: { deny: "native", channel: "exit2+json", failClosed: true },
|
|
26355
|
+
context: {
|
|
26356
|
+
sessionStart: "native",
|
|
26357
|
+
perTurn: "none",
|
|
26358
|
+
afterCompact: "partial",
|
|
26359
|
+
task: "single-active"
|
|
26360
|
+
},
|
|
26361
|
+
subagents: {
|
|
26362
|
+
identity: "native",
|
|
26363
|
+
parentBinding: "native",
|
|
26364
|
+
blockStart: "native",
|
|
26365
|
+
worktreeIsolation: "undocumented",
|
|
26366
|
+
maxConcurrency: "undocumented"
|
|
26367
|
+
},
|
|
26368
|
+
provenance: {
|
|
26369
|
+
sessionId: "native",
|
|
26370
|
+
agentIdOnTool: "undocumented",
|
|
26371
|
+
postToolObserve: "undocumented"
|
|
26372
|
+
},
|
|
26373
|
+
interaction: { questions: "none", writeBoundary: "partial" },
|
|
26374
|
+
stopControl: "undocumented",
|
|
26375
|
+
shellAvailable: "native",
|
|
26376
|
+
perEventCost: "npx-network",
|
|
26377
|
+
capabilities: [
|
|
25813
26378
|
{
|
|
25814
26379
|
name: "interactive_decision",
|
|
25815
26380
|
surface: "AskQuestion",
|
|
26381
|
+
refs: ["AskQuestion"],
|
|
26382
|
+
requires: [],
|
|
25816
26383
|
assurance: "agent_guided",
|
|
25817
|
-
reason: "Cursor does not expose AskQuestion answers to Workit hooks or MCP"
|
|
25818
|
-
refs: [hostRef("AskQuestion")]
|
|
26384
|
+
reason: "Cursor does not expose AskQuestion answers to Workit hooks or MCP"
|
|
25819
26385
|
},
|
|
25820
26386
|
{
|
|
25821
26387
|
name: "known_product_writes",
|
|
25822
26388
|
surface: "preToolUse",
|
|
26389
|
+
refs: ["preToolUse"],
|
|
26390
|
+
requires: [],
|
|
25823
26391
|
assurance: "unavailable",
|
|
25824
|
-
reason: "file writes are host-policy; the Cursor hook no longer gates write tools or shell commands"
|
|
25825
|
-
refs: [hostRef("preToolUse")]
|
|
26392
|
+
reason: "file writes are host-policy; the Cursor hook no longer gates write tools or shell commands"
|
|
25826
26393
|
},
|
|
25827
26394
|
{
|
|
25828
26395
|
name: "native_subagents",
|
|
25829
26396
|
surface: "subagentStart/subagentStop",
|
|
25830
|
-
|
|
25831
|
-
|
|
25832
|
-
|
|
26397
|
+
refs: ["subagentStart", "subagentStop"],
|
|
26398
|
+
requires: ["event:subagent.start", "event:subagent.stop"],
|
|
26399
|
+
observed: ["subagent.start", "subagent.stop"],
|
|
26400
|
+
assurance: "agent_guided",
|
|
26401
|
+
reason: "reviewer/investigator starts are bounded; Cursor implementer delegation is unavailable and subagentStop lacks a stable child identity",
|
|
26402
|
+
unavailableReason: "Cursor native subagent lifecycle hooks are incomplete"
|
|
25833
26403
|
},
|
|
25834
26404
|
{
|
|
25835
26405
|
name: "native_subagent_start",
|
|
25836
26406
|
surface: "subagentStart",
|
|
25837
|
-
|
|
25838
|
-
|
|
25839
|
-
|
|
26407
|
+
refs: ["subagentStart"],
|
|
26408
|
+
requires: ["event:subagent.start", "subagents.blockStart"],
|
|
26409
|
+
observed: ["subagent.start"],
|
|
26410
|
+
assurance: "enforced",
|
|
26411
|
+
reason: "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable",
|
|
26412
|
+
unavailableReason: "Cursor subagentStart is absent"
|
|
25840
26413
|
},
|
|
25841
26414
|
{
|
|
25842
26415
|
name: "fresh-context-review",
|
|
25843
26416
|
surface: "subagentStart",
|
|
25844
|
-
|
|
25845
|
-
|
|
25846
|
-
|
|
26417
|
+
refs: ["subagentStart"],
|
|
26418
|
+
requires: ["event:subagent.start"],
|
|
26419
|
+
observed: ["subagent.start"],
|
|
26420
|
+
assurance: "agent_guided",
|
|
26421
|
+
reason: "independent review runs as a bounded reviewer subagent; stops lack stable identity, so reviewer exclusivity is evaluated from recorded evidence",
|
|
26422
|
+
unavailableReason: "Cursor subagentStart is unavailable for independent review"
|
|
25847
26423
|
},
|
|
25848
26424
|
{
|
|
25849
26425
|
name: "arbitrary_shell_write",
|
|
25850
26426
|
surface: "unobservable_shell",
|
|
26427
|
+
refs: ["beforeShellExecution"],
|
|
26428
|
+
requires: [],
|
|
25851
26429
|
assurance: "unavailable",
|
|
25852
|
-
reason: "Only explicitly parsed shell targets are interceptable; arbitrary shell writes are not provable"
|
|
25853
|
-
refs: [hostRef("beforeShellExecution")]
|
|
26430
|
+
reason: "Only explicitly parsed shell targets are interceptable; arbitrary shell writes are not provable"
|
|
25854
26431
|
},
|
|
25855
26432
|
{
|
|
25856
26433
|
name: "compact_context",
|
|
25857
26434
|
surface: "sessionStart/preCompact",
|
|
25858
|
-
|
|
25859
|
-
|
|
25860
|
-
|
|
26435
|
+
refs: ["sessionStart", "preCompact"],
|
|
26436
|
+
requires: ["context.sessionStart"],
|
|
26437
|
+
observed: ["session.start"],
|
|
26438
|
+
assurance: "agent_guided",
|
|
26439
|
+
reason: "sessionStart injects context; preCompact can only show a bounded user reminder"
|
|
25861
26440
|
}
|
|
25862
|
-
]
|
|
26441
|
+
]
|
|
25863
26442
|
};
|
|
25864
|
-
var
|
|
25865
|
-
|
|
25866
|
-
|
|
25867
|
-
|
|
25868
|
-
|
|
25869
|
-
|
|
25870
|
-
"
|
|
25871
|
-
|
|
25872
|
-
|
|
26443
|
+
var BLOCKING = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
26444
|
+
|
|
26445
|
+
// packages/workit-core/src/hooks/run.ts
|
|
26446
|
+
if (false) {}
|
|
26447
|
+
// packages/workit-cursor/hooks/workit-hook.ts
|
|
26448
|
+
var cursorCapabilities = (availability = {}) => capabilitiesFor(CURSOR_DESCRIPTOR, {
|
|
26449
|
+
"session.start": availability.sessionStart,
|
|
26450
|
+
"tool.pre": availability.preToolUse,
|
|
26451
|
+
"shell.pre": availability.beforeShellExecution,
|
|
26452
|
+
"subagent.start": availability.subagentStart,
|
|
26453
|
+
"subagent.stop": availability.subagentStop
|
|
26454
|
+
});
|
|
25873
26455
|
if (false)
|
|
25874
26456
|
;
|
|
25875
26457
|
// packages/workit-cursor/mcp/run-server.ts
|