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