@brainervirus/workit-codex 2.1.5 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +1523 -994
- package/dist/workit-hook.js +3992 -2891
- package/package.json +3 -3
package/dist/launch-mcp.js
CHANGED
|
@@ -6625,7 +6625,7 @@ import { existsSync as existsSync8, realpathSync as realpathSync9, statSync as s
|
|
|
6625
6625
|
import path16 from "node:path";
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-core/src/core.ts
|
|
6628
|
-
import { spawnSync as
|
|
6628
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6629
6629
|
import { existsSync as existsSync5, realpathSync as realpathSync7 } from "node:fs";
|
|
6630
6630
|
import path11 from "node:path";
|
|
6631
6631
|
|
|
@@ -14487,7 +14487,8 @@ var hostSchema = _enum([
|
|
|
14487
14487
|
"codex_cli",
|
|
14488
14488
|
"codex_desktop",
|
|
14489
14489
|
"pi",
|
|
14490
|
-
"workit_cli"
|
|
14490
|
+
"workit_cli",
|
|
14491
|
+
"claude_code"
|
|
14491
14492
|
]);
|
|
14492
14493
|
var assuranceSchema = _enum(["enforced", "agent_guided", "unavailable"]);
|
|
14493
14494
|
var outcomeSchema = _enum(["verified", "accepted_limitations", "stopped"]);
|
|
@@ -14937,15 +14938,70 @@ var taskRecordSchema = object({
|
|
|
14937
14938
|
decisions: array(entrySchema(decisionSchema)),
|
|
14938
14939
|
actionProgress: actionProgressListSchema.optional(),
|
|
14939
14940
|
findings: array(entrySchema(findingSchema)),
|
|
14940
|
-
workers: array(entrySchema(workerSchema))
|
|
14941
|
+
workers: array(entrySchema(workerSchema)),
|
|
14942
|
+
critical: array(nonEmpty).optional()
|
|
14941
14943
|
}).strict();
|
|
14944
|
+
var stripCount = (strips) => strips.reduce((sum, strip) => sum + strip.keys.length, 0);
|
|
14945
|
+
var strippable = (issues, prefix = []) => {
|
|
14946
|
+
const strips = [];
|
|
14947
|
+
for (const issue of issues) {
|
|
14948
|
+
if (issue.code === "unrecognized_keys") {
|
|
14949
|
+
strips.push({ path: [...prefix, ...issue.path], keys: issue.keys });
|
|
14950
|
+
continue;
|
|
14951
|
+
}
|
|
14952
|
+
if (issue.code !== "invalid_union")
|
|
14953
|
+
return null;
|
|
14954
|
+
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);
|
|
14955
|
+
if (!branch)
|
|
14956
|
+
return null;
|
|
14957
|
+
strips.push(...branch);
|
|
14958
|
+
}
|
|
14959
|
+
return strips;
|
|
14960
|
+
};
|
|
14961
|
+
var recordPath = (path) => path.map((key) => typeof key === "number" ? "*" : String(key)).join(".");
|
|
14962
|
+
var overlaps = (left, right) => left === right || left.startsWith(`${right}.`) || right.startsWith(`${left}.`);
|
|
14963
|
+
var parseStoredRecord = (schema, value) => {
|
|
14964
|
+
let parsed = schema.safeParse(value);
|
|
14965
|
+
if (parsed.success)
|
|
14966
|
+
return { success: true, data: parsed.data, stripped: [] };
|
|
14967
|
+
const first = { success: false, error: parsed.error, critical: [] };
|
|
14968
|
+
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(".")) : [];
|
|
14969
|
+
const stripped = [];
|
|
14970
|
+
let current = structuredClone(value);
|
|
14971
|
+
for (let round = 0;round < 8 && !parsed.success; round++) {
|
|
14972
|
+
const strips = strippable(parsed.error.issues);
|
|
14973
|
+
if (strips === null || strips.length === 0)
|
|
14974
|
+
return first;
|
|
14975
|
+
for (const strip of strips) {
|
|
14976
|
+
let target = current;
|
|
14977
|
+
for (const key of strip.path)
|
|
14978
|
+
target = typeof target === "object" && target !== null ? target[key] : undefined;
|
|
14979
|
+
if (typeof target !== "object" || target === null)
|
|
14980
|
+
return first;
|
|
14981
|
+
for (const key of strip.keys) {
|
|
14982
|
+
stripped.push(recordPath([...strip.path, key]));
|
|
14983
|
+
delete target[key];
|
|
14984
|
+
}
|
|
14985
|
+
}
|
|
14986
|
+
parsed = schema.safeParse(current);
|
|
14987
|
+
}
|
|
14988
|
+
if (!parsed.success)
|
|
14989
|
+
return first;
|
|
14990
|
+
const critical = [
|
|
14991
|
+
...new Set(stripped.filter((item) => declared.some((path) => overlaps(item, path))))
|
|
14992
|
+
];
|
|
14993
|
+
if (critical.length > 0)
|
|
14994
|
+
return { ...first, critical };
|
|
14995
|
+
return { success: true, data: parsed.data, stripped: [...new Set(stripped)] };
|
|
14996
|
+
};
|
|
14942
14997
|
var workspaceRecordSchema = object({
|
|
14943
14998
|
schemaVersion: literal(1),
|
|
14944
14999
|
id,
|
|
14945
15000
|
revision,
|
|
14946
15001
|
root: nonEmpty,
|
|
14947
15002
|
runtime: runtimeSchema.optional(),
|
|
14948
|
-
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
15003
|
+
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable(),
|
|
15004
|
+
critical: array(nonEmpty).optional()
|
|
14949
15005
|
}).strict();
|
|
14950
15006
|
var capabilitySchema = object({
|
|
14951
15007
|
name: nonEmpty,
|
|
@@ -15183,6 +15239,11 @@ var operationSchemas = {
|
|
|
15183
15239
|
writer: discriminatedUnion("action", Object.values(writerOperations)),
|
|
15184
15240
|
state: discriminatedUnion("action", Object.values(stateOperations))
|
|
15185
15241
|
};
|
|
15242
|
+
var { recover: _unadvertisedRecover, ...advertisedStateOperations } = stateOperations;
|
|
15243
|
+
var advertisedOperationSchemas = {
|
|
15244
|
+
...operationSchemas,
|
|
15245
|
+
state: discriminatedUnion("action", Object.values(advertisedStateOperations))
|
|
15246
|
+
};
|
|
15186
15247
|
var compiledOperationSchemas = Object.fromEntries(OPERATION_FAMILIES.map((family) => [family, compile(operationSchemas[family])]));
|
|
15187
15248
|
var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
15188
15249
|
ok: true,
|
|
@@ -15206,7 +15267,7 @@ function parseOperation(family, input) {
|
|
|
15206
15267
|
return failure2("invalid_input", "operation input is invalid", { fields });
|
|
15207
15268
|
}
|
|
15208
15269
|
function operationJsonSchema(family) {
|
|
15209
|
-
return toJSONSchema(
|
|
15270
|
+
return toJSONSchema(advertisedOperationSchemas[family], { target: "draft-2020-12" });
|
|
15210
15271
|
}
|
|
15211
15272
|
var OPERATION_SCHEMA_DEPTH = 1;
|
|
15212
15273
|
var schemaShapeDescription = (value, depth = 0) => {
|
|
@@ -15398,8 +15459,7 @@ function requirementId(input) {
|
|
|
15398
15459
|
// packages/workit-core/src/core/task-store.ts
|
|
15399
15460
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
15400
15461
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
15401
|
-
import * as
|
|
15402
|
-
import { hostname } from "node:os";
|
|
15462
|
+
import * as fs5 from "node:fs";
|
|
15403
15463
|
import path4 from "node:path";
|
|
15404
15464
|
|
|
15405
15465
|
// packages/workit-core/src/core/package-root.ts
|
|
@@ -16022,13 +16082,10 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
16022
16082
|
}
|
|
16023
16083
|
}
|
|
16024
16084
|
}
|
|
16025
|
-
// packages/workit-core/src/core/
|
|
16026
|
-
|
|
16027
|
-
|
|
16028
|
-
|
|
16029
|
-
processStart: string2().nullable(),
|
|
16030
|
-
ownerDigest: string2().regex(/^[0-9a-f]{64}$/).nullable()
|
|
16031
|
-
}).strict();
|
|
16085
|
+
// packages/workit-core/src/core/store-lock.ts
|
|
16086
|
+
import { spawnSync } from "node:child_process";
|
|
16087
|
+
import fs4 from "node:fs";
|
|
16088
|
+
import { hostname } from "node:os";
|
|
16032
16089
|
var metadataLockSchema = object({
|
|
16033
16090
|
pid: number2().int().nonnegative().safe(),
|
|
16034
16091
|
processStart: string2().nullable(),
|
|
@@ -16036,6 +16093,143 @@ var metadataLockSchema = object({
|
|
|
16036
16093
|
nonce: string2().min(1),
|
|
16037
16094
|
externalAction: literal(true).optional()
|
|
16038
16095
|
}).strict();
|
|
16096
|
+
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
16097
|
+
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
16098
|
+
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
16099
|
+
var defaultLockTimeoutMs = 250;
|
|
16100
|
+
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
16101
|
+
var parseMetadataLock = (raw) => {
|
|
16102
|
+
let value;
|
|
16103
|
+
try {
|
|
16104
|
+
value = JSON.parse(raw);
|
|
16105
|
+
} catch {
|
|
16106
|
+
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
16107
|
+
}
|
|
16108
|
+
const parsed = metadataLockSchema.safeParse(value);
|
|
16109
|
+
if (!parsed.success)
|
|
16110
|
+
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
16111
|
+
return parsed.data;
|
|
16112
|
+
};
|
|
16113
|
+
var parseMetadataLockOrNull = (raw) => {
|
|
16114
|
+
try {
|
|
16115
|
+
return parseMetadataLock(raw);
|
|
16116
|
+
} catch {
|
|
16117
|
+
return null;
|
|
16118
|
+
}
|
|
16119
|
+
};
|
|
16120
|
+
var sameMetadataLock = (left, right) => {
|
|
16121
|
+
const parsed = metadataLockSchema.safeParse(left);
|
|
16122
|
+
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
16123
|
+
};
|
|
16124
|
+
var parseProcStatStart = (stat) => {
|
|
16125
|
+
const close = stat.lastIndexOf(")");
|
|
16126
|
+
if (close < 0)
|
|
16127
|
+
return null;
|
|
16128
|
+
return stat.slice(close + 1).trim().split(/\s+/)[19] ?? null;
|
|
16129
|
+
};
|
|
16130
|
+
var processStartOf = (pid) => {
|
|
16131
|
+
if (process.platform === "linux") {
|
|
16132
|
+
try {
|
|
16133
|
+
return parseProcStatStart(fs4.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
16134
|
+
} catch {
|
|
16135
|
+
return null;
|
|
16136
|
+
}
|
|
16137
|
+
}
|
|
16138
|
+
if (process.platform === "darwin" || process.platform === "freebsd") {
|
|
16139
|
+
try {
|
|
16140
|
+
const run = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
16141
|
+
encoding: "utf8",
|
|
16142
|
+
timeout: 1000
|
|
16143
|
+
});
|
|
16144
|
+
const value = run.status === 0 ? run.stdout.trim() : "";
|
|
16145
|
+
return value || null;
|
|
16146
|
+
} catch {
|
|
16147
|
+
return null;
|
|
16148
|
+
}
|
|
16149
|
+
}
|
|
16150
|
+
return null;
|
|
16151
|
+
};
|
|
16152
|
+
var readTrimmed = (file) => {
|
|
16153
|
+
try {
|
|
16154
|
+
return fs4.readFileSync(file, "utf8").trim() || null;
|
|
16155
|
+
} catch {
|
|
16156
|
+
return null;
|
|
16157
|
+
}
|
|
16158
|
+
};
|
|
16159
|
+
var cachedLockHost = null;
|
|
16160
|
+
var localLockHost = () => {
|
|
16161
|
+
if (cachedLockHost !== null)
|
|
16162
|
+
return cachedLockHost;
|
|
16163
|
+
let pidns = null;
|
|
16164
|
+
try {
|
|
16165
|
+
pidns = /\[(\d+)\]/.exec(fs4.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
16166
|
+
} catch {}
|
|
16167
|
+
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
16168
|
+
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
16169
|
+
return cachedLockHost;
|
|
16170
|
+
};
|
|
16171
|
+
var pidAlive = (pid) => {
|
|
16172
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
16173
|
+
return false;
|
|
16174
|
+
try {
|
|
16175
|
+
process.kill(pid, 0);
|
|
16176
|
+
return true;
|
|
16177
|
+
} catch (error) {
|
|
16178
|
+
return error.code === "EPERM";
|
|
16179
|
+
}
|
|
16180
|
+
};
|
|
16181
|
+
var classifyLockOwner = (payload, ageMs, localHost = localLockHost()) => {
|
|
16182
|
+
const lock = metadataLockSchema.safeParse(payload);
|
|
16183
|
+
if (!lock.success)
|
|
16184
|
+
return ageMs !== null && ageMs > UNREADABLE_LOCK_TTL_MS ? { state: "stale", reason: "unreadable lock left behind" } : { state: "unknown", reason: "lock is being written" };
|
|
16185
|
+
const { pid, processStart, host } = lock.data;
|
|
16186
|
+
const [lockName, lockSpace] = host.split("#");
|
|
16187
|
+
const [localName, localSpace] = localHost.split("#");
|
|
16188
|
+
if (lockSpace && localSpace && lockName === localName) {
|
|
16189
|
+
const [lockNs, lockBoot] = lockSpace.split(":");
|
|
16190
|
+
const [localNs, localBoot] = localSpace.split(":");
|
|
16191
|
+
if (lockNs === localNs && lockNs !== "?" && lockBoot !== "?" && lockBoot !== localBoot)
|
|
16192
|
+
return { state: "stale", reason: "lock was taken before this machine rebooted" };
|
|
16193
|
+
}
|
|
16194
|
+
if (host !== localHost)
|
|
16195
|
+
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` };
|
|
16196
|
+
if (!pidAlive(pid))
|
|
16197
|
+
return { state: "stale", reason: `pid ${pid} is not running` };
|
|
16198
|
+
const currentStart = processStartOf(pid);
|
|
16199
|
+
if (processStart !== null && currentStart !== null && processStart !== currentStart)
|
|
16200
|
+
return { state: "stale", reason: `pid ${pid} now belongs to a different process` };
|
|
16201
|
+
return { state: "live", reason: `held by running pid ${pid}` };
|
|
16202
|
+
};
|
|
16203
|
+
var ageOf = (file, nowMs) => {
|
|
16204
|
+
try {
|
|
16205
|
+
return nowMs - fs4.lstatSync(file).mtimeMs;
|
|
16206
|
+
} catch {
|
|
16207
|
+
return null;
|
|
16208
|
+
}
|
|
16209
|
+
};
|
|
16210
|
+
var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
16211
|
+
const guard = `${lockPath}.reclaim`;
|
|
16212
|
+
const age = ageOf(guard, nowMs);
|
|
16213
|
+
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
16214
|
+
return false;
|
|
16215
|
+
try {
|
|
16216
|
+
fs4.rmdirSync(guard);
|
|
16217
|
+
return true;
|
|
16218
|
+
} catch {
|
|
16219
|
+
return false;
|
|
16220
|
+
}
|
|
16221
|
+
};
|
|
16222
|
+
|
|
16223
|
+
// packages/workit-core/src/core/task-store.ts
|
|
16224
|
+
var RECOVERY_COPIES_PER_RECORD = 3;
|
|
16225
|
+
var STALE_TEMPORARY_MS = 60 * 60000;
|
|
16226
|
+
var RECOVERY_NAME = /^(task|workspace)\.([^.]+)\.([0-9a-f]{64})\.json$/;
|
|
16227
|
+
var processEvidenceSchema = object({
|
|
16228
|
+
state: _enum(["stopped", "accounted_for"]),
|
|
16229
|
+
pid: number2().int().nonnegative(),
|
|
16230
|
+
processStart: string2().nullable(),
|
|
16231
|
+
ownerDigest: string2().regex(/^[0-9a-f]{64}$/).nullable()
|
|
16232
|
+
}).strict();
|
|
16039
16233
|
var INDEX_VERSION = 1;
|
|
16040
16234
|
var hostSession = (value) => isObject2(value) && value.kind === "host" && typeof value.host === "string" && typeof value.handle === "string" ? { host: value.host, handle: value.handle } : null;
|
|
16041
16235
|
var indexEntry = (task, file) => {
|
|
@@ -16063,7 +16257,7 @@ var indexEntry = (task, file) => {
|
|
|
16063
16257
|
};
|
|
16064
16258
|
var fileSignature = (file) => {
|
|
16065
16259
|
try {
|
|
16066
|
-
const stat =
|
|
16260
|
+
const stat = fs5.statSync(file, { bigint: true });
|
|
16067
16261
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
16068
16262
|
} catch {
|
|
16069
16263
|
return null;
|
|
@@ -16080,7 +16274,7 @@ var cachedRuntimeVersion = null;
|
|
|
16080
16274
|
var runtimeVersion = () => {
|
|
16081
16275
|
if (cachedRuntimeVersion === null) {
|
|
16082
16276
|
try {
|
|
16083
|
-
const pkg = JSON.parse(
|
|
16277
|
+
const pkg = JSON.parse(fs5.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
16084
16278
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
16085
16279
|
} catch {
|
|
16086
16280
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -16109,36 +16303,20 @@ var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
|
16109
16303
|
var isObject2 = (value) => typeof value === "object" && value !== null;
|
|
16110
16304
|
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);
|
|
16111
16305
|
var validDigest = (value) => /^[0-9a-f]{64}$/.test(value);
|
|
16112
|
-
var parseMetadataLock = (raw) => {
|
|
16113
|
-
let value;
|
|
16114
|
-
try {
|
|
16115
|
-
value = JSON.parse(raw);
|
|
16116
|
-
} catch {
|
|
16117
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
16118
|
-
}
|
|
16119
|
-
const parsed = metadataLockSchema.safeParse(value);
|
|
16120
|
-
if (!parsed.success)
|
|
16121
|
-
throw Object.assign(new Error("metadata lock is invalid"), { code: "metadata_lock_invalid" });
|
|
16122
|
-
return parsed.data;
|
|
16123
|
-
};
|
|
16124
|
-
var sameMetadataLock = (left, right) => {
|
|
16125
|
-
const parsed = metadataLockSchema.safeParse(left);
|
|
16126
|
-
return parsed.success && canonicalJson(parsed.data) === canonicalJson(right);
|
|
16127
|
-
};
|
|
16128
16306
|
var sameDirectoryIdentity = (left, right) => {
|
|
16129
16307
|
if (!path4.isAbsolute(left) || !path4.isAbsolute(right))
|
|
16130
16308
|
return false;
|
|
16131
16309
|
const normalizedLeft = path4.resolve(left);
|
|
16132
16310
|
const normalizedRight = path4.resolve(right);
|
|
16133
16311
|
try {
|
|
16134
|
-
const a =
|
|
16135
|
-
const b =
|
|
16312
|
+
const a = fs5.statSync(normalizedLeft, { bigint: true });
|
|
16313
|
+
const b = fs5.statSync(normalizedRight, { bigint: true });
|
|
16136
16314
|
if (!a.isDirectory() || !b.isDirectory())
|
|
16137
16315
|
return false;
|
|
16138
16316
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
16139
16317
|
return true;
|
|
16140
|
-
const canonicalLeft =
|
|
16141
|
-
const canonicalRight =
|
|
16318
|
+
const canonicalLeft = fs5.realpathSync(normalizedLeft);
|
|
16319
|
+
const canonicalRight = fs5.realpathSync(normalizedRight);
|
|
16142
16320
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
16143
16321
|
return true;
|
|
16144
16322
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -16146,12 +16324,47 @@ var sameDirectoryIdentity = (left, right) => {
|
|
|
16146
16324
|
return false;
|
|
16147
16325
|
}
|
|
16148
16326
|
};
|
|
16327
|
+
var TRANSIENT_WINDOWS_CODES = new Set(["EPERM", "EACCES", "EBUSY"]);
|
|
16328
|
+
var isTransientWindowsError = (error) => {
|
|
16329
|
+
if (process.platform !== "win32")
|
|
16330
|
+
return false;
|
|
16331
|
+
const code = error?.code;
|
|
16332
|
+
return typeof code === "string" && TRANSIENT_WINDOWS_CODES.has(code);
|
|
16333
|
+
};
|
|
16334
|
+
var retryTransient = (run) => {
|
|
16335
|
+
if (process.platform !== "win32")
|
|
16336
|
+
return run();
|
|
16337
|
+
for (let attempt = 0;; attempt += 1) {
|
|
16338
|
+
try {
|
|
16339
|
+
return run();
|
|
16340
|
+
} catch (error) {
|
|
16341
|
+
if (attempt >= 20 || !isTransientWindowsError(error))
|
|
16342
|
+
throw error;
|
|
16343
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5 * (attempt + 1));
|
|
16344
|
+
}
|
|
16345
|
+
}
|
|
16346
|
+
};
|
|
16149
16347
|
var externalActionLockRoots = new AsyncLocalStorage;
|
|
16348
|
+
var heldInProcess = new Map;
|
|
16349
|
+
var holdRoot = (root) => heldInProcess.set(root, (heldInProcess.get(root) ?? 0) + 1);
|
|
16350
|
+
var dropRoot = (root) => {
|
|
16351
|
+
const count = (heldInProcess.get(root) ?? 1) - 1;
|
|
16352
|
+
if (count > 0)
|
|
16353
|
+
heldInProcess.set(root, count);
|
|
16354
|
+
else
|
|
16355
|
+
heldInProcess.delete(root);
|
|
16356
|
+
};
|
|
16357
|
+
var dedupeCandidates = (candidates) => {
|
|
16358
|
+
const last = new Map(candidates.map((candidate, index) => [candidate.id, index]));
|
|
16359
|
+
return candidates.filter((candidate, index) => last.get(candidate.id) === index);
|
|
16360
|
+
};
|
|
16150
16361
|
|
|
16151
16362
|
class TaskStore {
|
|
16152
16363
|
root;
|
|
16153
|
-
|
|
16154
|
-
|
|
16364
|
+
lockTimeoutMs;
|
|
16365
|
+
constructor(root, options = {}) {
|
|
16366
|
+
this.root = fs5.existsSync(root) ? fs5.realpathSync(root) : path4.resolve(root);
|
|
16367
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
16155
16368
|
}
|
|
16156
16369
|
readTask(taskId) {
|
|
16157
16370
|
if (!validId(taskId))
|
|
@@ -16171,11 +16384,11 @@ class TaskStore {
|
|
|
16171
16384
|
return result.result;
|
|
16172
16385
|
}
|
|
16173
16386
|
listTasks() {
|
|
16174
|
-
if (!
|
|
16387
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
16175
16388
|
return success(null, null, []);
|
|
16176
16389
|
let names;
|
|
16177
16390
|
try {
|
|
16178
|
-
names =
|
|
16391
|
+
names = fs5.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
16179
16392
|
} catch (error) {
|
|
16180
16393
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
16181
16394
|
path: this.tasksDir
|
|
@@ -16204,11 +16417,11 @@ class TaskStore {
|
|
|
16204
16417
|
return success(null, null, tasks);
|
|
16205
16418
|
}
|
|
16206
16419
|
listTaskIndex() {
|
|
16207
|
-
if (!
|
|
16420
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
16208
16421
|
return success(null, null, []);
|
|
16209
16422
|
let names;
|
|
16210
16423
|
try {
|
|
16211
|
-
names =
|
|
16424
|
+
names = fs5.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
|
|
16212
16425
|
} catch (error) {
|
|
16213
16426
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
16214
16427
|
path: this.tasksDir
|
|
@@ -16470,7 +16683,7 @@ class TaskStore {
|
|
|
16470
16683
|
if (!current.data)
|
|
16471
16684
|
return failure2("not_found", "workspace not found");
|
|
16472
16685
|
if (current.data.revision !== expected)
|
|
16473
|
-
return this.
|
|
16686
|
+
return this.workspaceConflict(expected, current.data.revision);
|
|
16474
16687
|
const previousBytes = this.snapshotBytes(this.workspacePath);
|
|
16475
16688
|
if (!previousBytes)
|
|
16476
16689
|
return failure2("storage_error", "workspace snapshot disappeared during mutation");
|
|
@@ -16513,7 +16726,7 @@ class TaskStore {
|
|
|
16513
16726
|
if (task.data.revision !== expected)
|
|
16514
16727
|
return this.conflict(expected, task.data.revision);
|
|
16515
16728
|
if (workspace.data.revision !== input.expectedWorkspaceRevision)
|
|
16516
|
-
return this.
|
|
16729
|
+
return this.workspaceConflict(input.expectedWorkspaceRevision, workspace.data.revision);
|
|
16517
16730
|
const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
|
|
16518
16731
|
const previousTaskBytes = this.snapshotBytes(this.taskPath(input.taskId));
|
|
16519
16732
|
if (!previousWorkspaceBytes || !previousTaskBytes)
|
|
@@ -16576,12 +16789,12 @@ class TaskStore {
|
|
|
16576
16789
|
});
|
|
16577
16790
|
}
|
|
16578
16791
|
recoveryCandidates() {
|
|
16579
|
-
if (!
|
|
16792
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
16580
16793
|
return success(null, null, []);
|
|
16581
16794
|
try {
|
|
16582
16795
|
const candidates = [];
|
|
16583
|
-
for (const name of
|
|
16584
|
-
const match =
|
|
16796
|
+
for (const name of fs5.readdirSync(this.recoveryDir)) {
|
|
16797
|
+
const match = RECOVERY_NAME.exec(name);
|
|
16585
16798
|
if (match)
|
|
16586
16799
|
candidates.push({
|
|
16587
16800
|
target: match[1],
|
|
@@ -16629,7 +16842,7 @@ class TaskStore {
|
|
|
16629
16842
|
return failure2("recovery_required", "validated recovery snapshot not found");
|
|
16630
16843
|
selectedBytes = candidate;
|
|
16631
16844
|
}
|
|
16632
|
-
const selectedRecord = target === "workspace" ? this.parseBytes(selectedBytes, workspaceRecordSchema) : this.parseBytes(selectedBytes, taskRecordSchema);
|
|
16845
|
+
const selectedRecord = target === "workspace" ? this.parseBytes(selectedBytes, workspaceRecordSchema, "rewrite") : this.parseBytes(selectedBytes, taskRecordSchema, "rewrite");
|
|
16633
16846
|
if (!selectedRecord.ok)
|
|
16634
16847
|
return selectedRecord;
|
|
16635
16848
|
if (target === "workspace") {
|
|
@@ -16675,14 +16888,14 @@ class TaskStore {
|
|
|
16675
16888
|
return this.conflict(input.expectedWorkspaceRevision, currentWorkspace.data.revision);
|
|
16676
16889
|
}
|
|
16677
16890
|
if (target === "workspace") {
|
|
16678
|
-
const parsed = this.parseBytes(selectedBytes, workspaceRecordSchema);
|
|
16891
|
+
const parsed = this.parseBytes(selectedBytes, workspaceRecordSchema, "rewrite");
|
|
16679
16892
|
if (!parsed.ok)
|
|
16680
16893
|
return parsed;
|
|
16681
16894
|
const value = { ...parsed.data, revision: newRevision(), writer: null };
|
|
16682
16895
|
const replaced = this.replaceSnapshot(file, value, reacquiredBytes);
|
|
16683
16896
|
return replaced.ok ? success(value.revision, value.revision, value) : replaced;
|
|
16684
16897
|
}
|
|
16685
|
-
const parsed = this.parseBytes(selectedBytes, taskRecordSchema);
|
|
16898
|
+
const parsed = this.parseBytes(selectedBytes, taskRecordSchema, "rewrite");
|
|
16686
16899
|
if (!parsed.ok)
|
|
16687
16900
|
return parsed;
|
|
16688
16901
|
const value = {
|
|
@@ -16693,7 +16906,7 @@ class TaskStore {
|
|
|
16693
16906
|
};
|
|
16694
16907
|
const replaced = this.replaceSnapshot(file, value, reacquiredBytes);
|
|
16695
16908
|
return replaced.ok ? success(value.revision, null, value) : replaced;
|
|
16696
|
-
}, this.recoveryLockOptions(lock.data, evidence.data, recoveryGate));
|
|
16909
|
+
}, this.recoveryLockOptions(lock.data, evidence.data, recoveryGate), "recovery_required");
|
|
16697
16910
|
return result;
|
|
16698
16911
|
} catch (error) {
|
|
16699
16912
|
return failure2("recovery_required", `recovery protocol failed: ${String(error)}`);
|
|
@@ -16728,12 +16941,12 @@ class TaskStore {
|
|
|
16728
16941
|
}
|
|
16729
16942
|
readLockSnapshot() {
|
|
16730
16943
|
try {
|
|
16731
|
-
const stat =
|
|
16944
|
+
const stat = fs5.lstatSync(this.lockPath);
|
|
16732
16945
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
16733
16946
|
return failure2("recovery_required", "metadata lock is not a regular file", {
|
|
16734
16947
|
path: this.lockPath
|
|
16735
16948
|
});
|
|
16736
|
-
const raw =
|
|
16949
|
+
const raw = fs5.readFileSync(this.lockPath, "utf8");
|
|
16737
16950
|
return success(null, null, { raw, data: parseMetadataLock(raw) });
|
|
16738
16951
|
} catch (error) {
|
|
16739
16952
|
if (error?.code === "ENOENT")
|
|
@@ -16744,22 +16957,50 @@ class TaskStore {
|
|
|
16744
16957
|
}
|
|
16745
16958
|
}
|
|
16746
16959
|
metadataLockOptions() {
|
|
16960
|
+
const inProcess = heldInProcess.has(this.root);
|
|
16747
16961
|
return {
|
|
16748
16962
|
lockPath: this.lockPath,
|
|
16749
16963
|
staleMs: Number.MAX_SAFE_INTEGER,
|
|
16750
|
-
timeoutMs: 0,
|
|
16751
|
-
retry: { retries: 0 },
|
|
16752
|
-
staleRecovery: "
|
|
16753
|
-
shouldReclaim: () =>
|
|
16754
|
-
|
|
16964
|
+
timeoutMs: inProcess ? 0 : this.lockTimeoutMs,
|
|
16965
|
+
retry: inProcess ? { retries: 0 } : { minTimeout: 5, maxTimeout: 100, factor: 1.5, randomize: true },
|
|
16966
|
+
staleRecovery: "remove-if-unchanged",
|
|
16967
|
+
shouldReclaim: ({ payload, nowMs }) => {
|
|
16968
|
+
let ageMs = null;
|
|
16969
|
+
try {
|
|
16970
|
+
ageMs = nowMs - fs5.lstatSync(this.lockPath).mtimeMs;
|
|
16971
|
+
} catch {}
|
|
16972
|
+
return classifyLockOwner(payload, ageMs).state === "stale";
|
|
16973
|
+
},
|
|
16974
|
+
shouldRemoveStaleLock: () => true,
|
|
16975
|
+
parsePayload: parseMetadataLockOrNull,
|
|
16755
16976
|
payload: () => ({
|
|
16756
16977
|
pid: process.pid,
|
|
16757
|
-
processStart:
|
|
16758
|
-
host:
|
|
16978
|
+
processStart: processStartOf(process.pid),
|
|
16979
|
+
host: localLockHost(),
|
|
16759
16980
|
nonce: randomUUID2()
|
|
16760
16981
|
})
|
|
16761
16982
|
};
|
|
16762
16983
|
}
|
|
16984
|
+
acquireMetadataLock(options) {
|
|
16985
|
+
clearAbandonedReclaimGuard(this.lockPath);
|
|
16986
|
+
const deadline = Date.now() + (options.timeoutMs ?? 0);
|
|
16987
|
+
while (true) {
|
|
16988
|
+
try {
|
|
16989
|
+
return acquireFileLockSync(this.workspacePath, {
|
|
16990
|
+
...options,
|
|
16991
|
+
timeoutMs: Math.max(0, deadline - Date.now())
|
|
16992
|
+
});
|
|
16993
|
+
} catch (error) {
|
|
16994
|
+
const code = error?.code;
|
|
16995
|
+
if (code !== "file_lock_stale" && !isTransientWindowsError(error) || Date.now() >= deadline)
|
|
16996
|
+
throw error;
|
|
16997
|
+
if (code !== "file_lock_stale" && !this.lockHolderPresent() && !this.lockDirWritable())
|
|
16998
|
+
throw error;
|
|
16999
|
+
if (code !== "file_lock_stale")
|
|
17000
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5);
|
|
17001
|
+
}
|
|
17002
|
+
}
|
|
17003
|
+
}
|
|
16763
17004
|
externalActionLockOptions() {
|
|
16764
17005
|
const options = this.metadataLockOptions();
|
|
16765
17006
|
return { ...options, payload: () => ({ ...options.payload(), externalAction: true }) };
|
|
@@ -16777,10 +17018,11 @@ class TaskStore {
|
|
|
16777
17018
|
}
|
|
16778
17019
|
let handle;
|
|
16779
17020
|
try {
|
|
16780
|
-
handle =
|
|
17021
|
+
handle = this.acquireMetadataLock(this.externalActionLockOptions());
|
|
16781
17022
|
} catch (error) {
|
|
16782
17023
|
return this.lockFailure(error);
|
|
16783
17024
|
}
|
|
17025
|
+
holdRoot(this.root);
|
|
16784
17026
|
let result;
|
|
16785
17027
|
try {
|
|
16786
17028
|
if (!handle.verifyStillHeld())
|
|
@@ -16815,8 +17057,9 @@ class TaskStore {
|
|
|
16815
17057
|
path: this.lockPath
|
|
16816
17058
|
});
|
|
16817
17059
|
}
|
|
17060
|
+
dropRoot(this.root);
|
|
16818
17061
|
try {
|
|
16819
|
-
handle.release();
|
|
17062
|
+
retryTransient(() => handle.release());
|
|
16820
17063
|
} catch (error) {
|
|
16821
17064
|
const released = this.lockFailure(error);
|
|
16822
17065
|
const releaseError = released.ok ? "metadata lock release failed" : released.error;
|
|
@@ -16829,6 +17072,9 @@ class TaskStore {
|
|
|
16829
17072
|
}
|
|
16830
17073
|
recoveryLockOptions(observed, evidence, gate) {
|
|
16831
17074
|
const options = this.metadataLockOptions();
|
|
17075
|
+
options.timeoutMs = 0;
|
|
17076
|
+
options.retry = { retries: 0 };
|
|
17077
|
+
options.parsePayload = parseMetadataLock;
|
|
16832
17078
|
options.staleRecovery = "remove-if-unchanged";
|
|
16833
17079
|
options.shouldReclaim = ({ payload }) => Boolean(observed && sameMetadataLock(payload, observed.data) && (evidence.state === "stopped" || evidence.state === "accounted_for"));
|
|
16834
17080
|
options.shouldRemoveStaleLock = ({ raw, payload }) => {
|
|
@@ -16850,7 +17096,7 @@ class TaskStore {
|
|
|
16850
17096
|
};
|
|
16851
17097
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
16852
17098
|
}
|
|
16853
|
-
withLock(operation, options = this.metadataLockOptions()) {
|
|
17099
|
+
withLock(operation, options = this.metadataLockOptions(), contention = "busy") {
|
|
16854
17100
|
try {
|
|
16855
17101
|
this.initializeMutationStorage();
|
|
16856
17102
|
} catch (error) {
|
|
@@ -16861,10 +17107,12 @@ class TaskStore {
|
|
|
16861
17107
|
let handle;
|
|
16862
17108
|
let result = failure2("storage_error", "metadata lock operation did not produce a result");
|
|
16863
17109
|
try {
|
|
16864
|
-
handle =
|
|
17110
|
+
handle = this.acquireMetadataLock(options);
|
|
16865
17111
|
} catch (error) {
|
|
16866
|
-
result = this.lockFailure(error);
|
|
17112
|
+
result = this.lockFailure(error, contention);
|
|
16867
17113
|
}
|
|
17114
|
+
if (handle)
|
|
17115
|
+
holdRoot(this.root);
|
|
16868
17116
|
if (handle) {
|
|
16869
17117
|
try {
|
|
16870
17118
|
if (!handle.verifyStillHeld())
|
|
@@ -16883,8 +17131,9 @@ class TaskStore {
|
|
|
16883
17131
|
}
|
|
16884
17132
|
}
|
|
16885
17133
|
if (handle) {
|
|
17134
|
+
dropRoot(this.root);
|
|
16886
17135
|
try {
|
|
16887
|
-
handle.release();
|
|
17136
|
+
retryTransient(() => handle.release());
|
|
16888
17137
|
} catch (error) {
|
|
16889
17138
|
const releaseFailure = this.lockFailure(error);
|
|
16890
17139
|
const releaseError = releaseFailure.ok ? "metadata lock release failed" : releaseFailure.error;
|
|
@@ -16893,25 +17142,65 @@ class TaskStore {
|
|
|
16893
17142
|
}
|
|
16894
17143
|
return result;
|
|
16895
17144
|
}
|
|
16896
|
-
|
|
17145
|
+
lockDirWritable() {
|
|
17146
|
+
const probe = `${this.lockPath}.${process.pid}.${randomUUID2()}.probe`;
|
|
17147
|
+
try {
|
|
17148
|
+
fs5.closeSync(fs5.openSync(probe, "wx", 384));
|
|
17149
|
+
} catch {
|
|
17150
|
+
return false;
|
|
17151
|
+
}
|
|
17152
|
+
try {
|
|
17153
|
+
fs5.rmSync(probe, { force: true });
|
|
17154
|
+
} catch {}
|
|
17155
|
+
return true;
|
|
17156
|
+
}
|
|
17157
|
+
lockHolderPresent() {
|
|
17158
|
+
try {
|
|
17159
|
+
fs5.lstatSync(this.lockPath);
|
|
17160
|
+
return true;
|
|
17161
|
+
} catch (error) {
|
|
17162
|
+
return error?.code !== "ENOENT";
|
|
17163
|
+
}
|
|
17164
|
+
}
|
|
17165
|
+
lockFailure(error, contention = "busy") {
|
|
16897
17166
|
const value = error;
|
|
16898
17167
|
const code = typeof value?.code === "string" ? value.code : "";
|
|
16899
|
-
if (
|
|
16900
|
-
|
|
16901
|
-
|
|
17168
|
+
if (isTransientWindowsError(error) && !this.lockHolderPresent() && !this.lockDirWritable())
|
|
17169
|
+
return failure2("storage_error", `cannot create the workspace metadata lock (${code}); no other Workit call holds it`, {
|
|
17170
|
+
path: this.lockPath,
|
|
17171
|
+
guidance: `Check permissions and the read-only attribute on ${path4.dirname(this.lockPath)}.`
|
|
17172
|
+
});
|
|
17173
|
+
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
17174
|
+
let lock = null;
|
|
17175
|
+
try {
|
|
17176
|
+
lock = parseMetadataLockOrNull(fs5.readFileSync(this.lockPath, "utf8"));
|
|
17177
|
+
} catch {}
|
|
17178
|
+
if (lock?.externalAction)
|
|
16902
17179
|
return failure2("writer_conflict", "workspace is reserved by a managed external action", {
|
|
16903
17180
|
outcome: "not_started",
|
|
16904
17181
|
path: this.lockPath
|
|
16905
17182
|
});
|
|
17183
|
+
if (contention === "busy")
|
|
17184
|
+
return failure2("busy", `workspace metadata lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
17185
|
+
outcome: "not_started",
|
|
17186
|
+
path: this.lockPath,
|
|
17187
|
+
guidance: "Retry the same call. If it stays busy, run `workit doctor --fix-lock` to clear a lock left by a dead process."
|
|
17188
|
+
});
|
|
16906
17189
|
}
|
|
16907
17190
|
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));
|
|
16908
17191
|
return failure2(recovery ? "recovery_required" : "storage_error", `metadata lock operation failed: ${String(error)}`, { path: this.lockPath });
|
|
16909
17192
|
}
|
|
16910
17193
|
initializeMutationStorage() {
|
|
16911
|
-
|
|
16912
|
-
|
|
16913
|
-
|
|
16914
|
-
|
|
17194
|
+
retryTransient(() => fs5.mkdirSync(this.tasksDir, { recursive: true }));
|
|
17195
|
+
retryTransient(() => fs5.mkdirSync(this.recoveryDir, { recursive: true }));
|
|
17196
|
+
let current = null;
|
|
17197
|
+
try {
|
|
17198
|
+
current = retryTransient(() => fs5.readFileSync(this.gitignorePath, "utf8"));
|
|
17199
|
+
} catch {}
|
|
17200
|
+
if (current !== `*
|
|
17201
|
+
`)
|
|
17202
|
+
retryTransient(() => fs5.writeFileSync(this.gitignorePath, `*
|
|
17203
|
+
`));
|
|
16915
17204
|
}
|
|
16916
17205
|
replaceSnapshot(file, value, previous) {
|
|
16917
17206
|
let temporary;
|
|
@@ -16923,14 +17212,14 @@ class TaskStore {
|
|
|
16923
17212
|
else if (typeof previous === "string")
|
|
16924
17213
|
this.saveRecovery(file, previous);
|
|
16925
17214
|
temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
16926
|
-
const fd =
|
|
17215
|
+
const fd = fs5.openSync(temporary, "wx", 384);
|
|
16927
17216
|
try {
|
|
16928
|
-
|
|
16929
|
-
|
|
17217
|
+
fs5.writeSync(fd, jsonBytes(value));
|
|
17218
|
+
fs5.fsyncSync(fd);
|
|
16930
17219
|
} finally {
|
|
16931
|
-
|
|
17220
|
+
fs5.closeSync(fd);
|
|
16932
17221
|
}
|
|
16933
|
-
|
|
17222
|
+
retryTransient(() => fs5.renameSync(temporary, file));
|
|
16934
17223
|
temporary = undefined;
|
|
16935
17224
|
this.fsyncDirectory(path4.dirname(file));
|
|
16936
17225
|
if (path4.dirname(file) === this.tasksDir)
|
|
@@ -16943,13 +17232,13 @@ class TaskStore {
|
|
|
16943
17232
|
} finally {
|
|
16944
17233
|
if (temporary)
|
|
16945
17234
|
try {
|
|
16946
|
-
|
|
17235
|
+
fs5.unlinkSync(temporary);
|
|
16947
17236
|
} catch {}
|
|
16948
17237
|
}
|
|
16949
17238
|
}
|
|
16950
17239
|
readIndex(workspaceId) {
|
|
16951
17240
|
try {
|
|
16952
|
-
const value = JSON.parse(
|
|
17241
|
+
const value = JSON.parse(fs5.readFileSync(this.indexPath, "utf8"));
|
|
16953
17242
|
if (!isObject2(value) || value.version !== INDEX_VERSION || value.workspaceId !== workspaceId || !isObject2(value.tasks))
|
|
16954
17243
|
return {};
|
|
16955
17244
|
const tasks = {};
|
|
@@ -16964,15 +17253,15 @@ class TaskStore {
|
|
|
16964
17253
|
writeIndex(workspaceId, entries) {
|
|
16965
17254
|
const temporary = `${this.indexPath}.${process.pid}.${randomUUID2()}.tmp`;
|
|
16966
17255
|
try {
|
|
16967
|
-
|
|
17256
|
+
fs5.writeFileSync(temporary, JSON.stringify({
|
|
16968
17257
|
version: INDEX_VERSION,
|
|
16969
17258
|
workspaceId,
|
|
16970
17259
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
16971
17260
|
}), { mode: 384, flag: "wx" });
|
|
16972
|
-
|
|
17261
|
+
retryTransient(() => fs5.renameSync(temporary, this.indexPath));
|
|
16973
17262
|
} catch {
|
|
16974
17263
|
try {
|
|
16975
|
-
|
|
17264
|
+
fs5.unlinkSync(temporary);
|
|
16976
17265
|
} catch {}
|
|
16977
17266
|
}
|
|
16978
17267
|
}
|
|
@@ -16990,33 +17279,153 @@ class TaskStore {
|
|
|
16990
17279
|
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
16991
17280
|
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
16992
17281
|
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
16993
|
-
if (
|
|
16994
|
-
if (digestBytes(
|
|
16995
|
-
|
|
16996
|
-
|
|
17282
|
+
if (fs5.existsSync(destination)) {
|
|
17283
|
+
if (digestBytes(retryTransient(() => fs5.readFileSync(destination))) !== digestBytes(bytes))
|
|
17284
|
+
throw new Error("recovery copy already exists with different bytes");
|
|
17285
|
+
const stamp = new Date;
|
|
17286
|
+
retryTransient(() => fs5.utimesSync(destination, stamp, stamp));
|
|
17287
|
+
this.pruneRecovery(`${target}.${id}.`, destination);
|
|
17288
|
+
return;
|
|
16997
17289
|
}
|
|
16998
17290
|
let temporary;
|
|
16999
17291
|
try {
|
|
17000
17292
|
temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
|
|
17001
|
-
const fd =
|
|
17293
|
+
const fd = fs5.openSync(temporary, "wx", 384);
|
|
17002
17294
|
try {
|
|
17003
|
-
|
|
17004
|
-
|
|
17295
|
+
fs5.writeSync(fd, bytes);
|
|
17296
|
+
fs5.fsyncSync(fd);
|
|
17005
17297
|
} finally {
|
|
17006
|
-
|
|
17298
|
+
fs5.closeSync(fd);
|
|
17007
17299
|
}
|
|
17008
|
-
|
|
17300
|
+
retryTransient(() => fs5.renameSync(temporary, destination));
|
|
17009
17301
|
temporary = undefined;
|
|
17010
17302
|
this.fsyncDirectory(this.recoveryDir);
|
|
17303
|
+
this.pruneRecovery(`${target}.${id}.`, destination);
|
|
17011
17304
|
} finally {
|
|
17012
17305
|
if (temporary)
|
|
17013
17306
|
try {
|
|
17014
|
-
|
|
17307
|
+
fs5.unlinkSync(temporary);
|
|
17015
17308
|
} catch {}
|
|
17016
17309
|
}
|
|
17017
17310
|
}
|
|
17311
|
+
pruneRecovery(prefix, keep) {
|
|
17312
|
+
try {
|
|
17313
|
+
const copies = this.recoveryCopies(prefix);
|
|
17314
|
+
const surplus = this.surplusCopies(copies, path4.basename(keep));
|
|
17315
|
+
for (const copy of surplus)
|
|
17316
|
+
fs5.rmSync(copy.path, { force: true });
|
|
17317
|
+
} catch {}
|
|
17318
|
+
}
|
|
17319
|
+
recoveryCopies(prefix = "") {
|
|
17320
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
17321
|
+
return [];
|
|
17322
|
+
const copies = [];
|
|
17323
|
+
for (const name of fs5.readdirSync(this.recoveryDir)) {
|
|
17324
|
+
if (!name.startsWith(prefix))
|
|
17325
|
+
continue;
|
|
17326
|
+
const match = RECOVERY_NAME.exec(name);
|
|
17327
|
+
if (!match)
|
|
17328
|
+
continue;
|
|
17329
|
+
const file = path4.join(this.recoveryDir, name);
|
|
17330
|
+
try {
|
|
17331
|
+
const stat = fs5.lstatSync(file, { bigint: true });
|
|
17332
|
+
if (!stat.isFile())
|
|
17333
|
+
continue;
|
|
17334
|
+
copies.push({ name, path: file, group: `${match[1]}.${match[2]}`, mtimeNs: stat.mtimeNs });
|
|
17335
|
+
} catch {}
|
|
17336
|
+
}
|
|
17337
|
+
return copies;
|
|
17338
|
+
}
|
|
17339
|
+
surplusCopies(copies, keep) {
|
|
17340
|
+
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);
|
|
17341
|
+
return ordered.slice(RECOVERY_COPIES_PER_RECORD);
|
|
17342
|
+
}
|
|
17343
|
+
collectGarbage(options = {}) {
|
|
17344
|
+
const dryRun = options.dryRun === true;
|
|
17345
|
+
const report = {
|
|
17346
|
+
dryRun,
|
|
17347
|
+
recovery: { removed: 0, removedBytes: 0, kept: 0 },
|
|
17348
|
+
temporary: { removed: 0 },
|
|
17349
|
+
candidates: { removed: 0, tasks: [], skippedActive: [], skippedClosed: [], failed: [] }
|
|
17350
|
+
};
|
|
17351
|
+
if (!fs5.existsSync(this.workitDir))
|
|
17352
|
+
return success(null, null, report);
|
|
17353
|
+
const sweep = () => {
|
|
17354
|
+
try {
|
|
17355
|
+
const groups = new Map;
|
|
17356
|
+
for (const copy of this.recoveryCopies())
|
|
17357
|
+
groups.set(copy.group, [...groups.get(copy.group) ?? [], copy]);
|
|
17358
|
+
for (const copies of groups.values()) {
|
|
17359
|
+
const surplus = this.surplusCopies(copies);
|
|
17360
|
+
report.recovery.kept += copies.length - surplus.length;
|
|
17361
|
+
for (const copy of surplus) {
|
|
17362
|
+
report.recovery.removed += 1;
|
|
17363
|
+
try {
|
|
17364
|
+
report.recovery.removedBytes += fs5.statSync(copy.path).size;
|
|
17365
|
+
} catch {}
|
|
17366
|
+
if (!dryRun)
|
|
17367
|
+
fs5.rmSync(copy.path, { force: true });
|
|
17368
|
+
}
|
|
17369
|
+
}
|
|
17370
|
+
const nowMs = Date.now();
|
|
17371
|
+
for (const directory of [this.recoveryDir, this.tasksDir, this.workitDir]) {
|
|
17372
|
+
if (!fs5.existsSync(directory))
|
|
17373
|
+
continue;
|
|
17374
|
+
for (const name of fs5.readdirSync(directory)) {
|
|
17375
|
+
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
17376
|
+
continue;
|
|
17377
|
+
const file = path4.join(directory, name);
|
|
17378
|
+
const stat = fs5.lstatSync(file);
|
|
17379
|
+
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
17380
|
+
continue;
|
|
17381
|
+
report.temporary.removed += 1;
|
|
17382
|
+
if (!dryRun)
|
|
17383
|
+
fs5.rmSync(file, { force: true });
|
|
17384
|
+
}
|
|
17385
|
+
}
|
|
17386
|
+
return success(null, null, null);
|
|
17387
|
+
} catch (error) {
|
|
17388
|
+
return failure2("storage_error", `garbage collection failed: ${String(error)}`, {
|
|
17389
|
+
path: this.recoveryDir
|
|
17390
|
+
});
|
|
17391
|
+
}
|
|
17392
|
+
};
|
|
17393
|
+
const pruned = dryRun ? sweep() : this.withLock(sweep);
|
|
17394
|
+
if (!pruned.ok)
|
|
17395
|
+
return pruned;
|
|
17396
|
+
const tasks = this.listTasks();
|
|
17397
|
+
if (!tasks.ok)
|
|
17398
|
+
return tasks;
|
|
17399
|
+
for (const task of tasks.data) {
|
|
17400
|
+
const deduped = dedupeCandidates(task.candidates);
|
|
17401
|
+
const removed = task.candidates.length - deduped.length;
|
|
17402
|
+
if (removed === 0)
|
|
17403
|
+
continue;
|
|
17404
|
+
if (task.status === "active") {
|
|
17405
|
+
report.candidates.skippedActive.push(task.id);
|
|
17406
|
+
continue;
|
|
17407
|
+
}
|
|
17408
|
+
if (task.status === "closed") {
|
|
17409
|
+
report.candidates.skippedClosed.push(task.id);
|
|
17410
|
+
continue;
|
|
17411
|
+
}
|
|
17412
|
+
if (!dryRun) {
|
|
17413
|
+
const written = this.mutateTask(task.id, task.revision, (current) => success(current.revision, null, {
|
|
17414
|
+
...current,
|
|
17415
|
+
candidates: dedupeCandidates(current.candidates)
|
|
17416
|
+
}));
|
|
17417
|
+
if (!written.ok) {
|
|
17418
|
+
report.candidates.failed.push(task.id);
|
|
17419
|
+
continue;
|
|
17420
|
+
}
|
|
17421
|
+
}
|
|
17422
|
+
report.candidates.removed += removed;
|
|
17423
|
+
report.candidates.tasks.push(task.id);
|
|
17424
|
+
}
|
|
17425
|
+
return success(null, null, report);
|
|
17426
|
+
}
|
|
17018
17427
|
findRecovery(target, taskId, digest, workspace) {
|
|
17019
|
-
if (!
|
|
17428
|
+
if (!fs5.existsSync(this.recoveryDir))
|
|
17020
17429
|
return null;
|
|
17021
17430
|
const workspaceId = workspace?.id ?? this.trustedWorkspaceId();
|
|
17022
17431
|
if (target === "workspace" && !workspaceId)
|
|
@@ -17028,9 +17437,9 @@ class TaskStore {
|
|
|
17028
17437
|
if (candidate.target !== target || candidate.digest !== digest)
|
|
17029
17438
|
continue;
|
|
17030
17439
|
try {
|
|
17031
|
-
if (!
|
|
17440
|
+
if (!fs5.lstatSync(candidate.path).isFile())
|
|
17032
17441
|
continue;
|
|
17033
|
-
const bytes =
|
|
17442
|
+
const bytes = fs5.readFileSync(candidate.path);
|
|
17034
17443
|
if (digestBytes(bytes) !== digest)
|
|
17035
17444
|
continue;
|
|
17036
17445
|
if (target === "workspace") {
|
|
@@ -17048,11 +17457,11 @@ class TaskStore {
|
|
|
17048
17457
|
}
|
|
17049
17458
|
fsyncDirectory(directory) {
|
|
17050
17459
|
try {
|
|
17051
|
-
const fd =
|
|
17460
|
+
const fd = fs5.openSync(directory, "r");
|
|
17052
17461
|
try {
|
|
17053
|
-
|
|
17462
|
+
fs5.fsyncSync(fd);
|
|
17054
17463
|
} finally {
|
|
17055
|
-
|
|
17464
|
+
fs5.closeSync(fd);
|
|
17056
17465
|
}
|
|
17057
17466
|
} catch (error) {
|
|
17058
17467
|
if (process.platform !== "win32")
|
|
@@ -17060,14 +17469,14 @@ class TaskStore {
|
|
|
17060
17469
|
}
|
|
17061
17470
|
}
|
|
17062
17471
|
trustedWorkspaceId() {
|
|
17063
|
-
if (!
|
|
17472
|
+
if (!fs5.existsSync(this.tasksDir))
|
|
17064
17473
|
return null;
|
|
17065
17474
|
const ids = new Set;
|
|
17066
17475
|
try {
|
|
17067
|
-
for (const name of
|
|
17476
|
+
for (const name of fs5.readdirSync(this.tasksDir)) {
|
|
17068
17477
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
17069
17478
|
return null;
|
|
17070
|
-
const parsed = this.parseBytes(
|
|
17479
|
+
const parsed = this.parseBytes(fs5.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
17071
17480
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
17072
17481
|
return null;
|
|
17073
17482
|
ids.add(parsed.data.workspaceId);
|
|
@@ -17079,14 +17488,14 @@ class TaskStore {
|
|
|
17079
17488
|
}
|
|
17080
17489
|
snapshotBytes(file) {
|
|
17081
17490
|
try {
|
|
17082
|
-
return
|
|
17491
|
+
return retryTransient(() => fs5.readFileSync(file));
|
|
17083
17492
|
} catch {
|
|
17084
17493
|
return null;
|
|
17085
17494
|
}
|
|
17086
17495
|
}
|
|
17087
17496
|
readRecord(file, schema) {
|
|
17088
17497
|
try {
|
|
17089
|
-
const bytes =
|
|
17498
|
+
const bytes = retryTransient(() => fs5.readFileSync(file, "utf8"));
|
|
17090
17499
|
return { exists: true, result: this.parseBytes(bytes, schema) };
|
|
17091
17500
|
} catch (error) {
|
|
17092
17501
|
if (error?.code === "ENOENT")
|
|
@@ -17099,7 +17508,7 @@ class TaskStore {
|
|
|
17099
17508
|
};
|
|
17100
17509
|
}
|
|
17101
17510
|
}
|
|
17102
|
-
parseBytes(bytes, schema) {
|
|
17511
|
+
parseBytes(bytes, schema, mode = "read") {
|
|
17103
17512
|
let value;
|
|
17104
17513
|
try {
|
|
17105
17514
|
value = JSON.parse(typeof bytes === "string" ? bytes : bytes.toString("utf8"));
|
|
@@ -17108,27 +17517,28 @@ class TaskStore {
|
|
|
17108
17517
|
}
|
|
17109
17518
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
17110
17519
|
return failure2("unsupported_version", "unsupported snapshot schema version");
|
|
17111
|
-
const parsed = schema
|
|
17112
|
-
if (parsed.success)
|
|
17520
|
+
const parsed = parseStoredRecord(schema, value);
|
|
17521
|
+
if (parsed.success && (mode === "read" || parsed.stripped.length === 0))
|
|
17113
17522
|
return success(null, null, parsed.data);
|
|
17114
17523
|
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
17524
|
+
const upgrade = `upgrade Workit before ${parsed.success ? "recovering" : "mutating"} this checkout`;
|
|
17525
|
+
if (parsed.success)
|
|
17526
|
+
return failure2("recovery_required", `snapshot carries fields this Workit cannot preserve (${parsed.stripped.join(", ")}); ${upgrade}`);
|
|
17527
|
+
if (parsed.critical.length > 0)
|
|
17528
|
+
return failure2("recovery_required", `snapshot requires fields this Workit cannot read (${parsed.critical.join(", ")}); ${upgrade}`);
|
|
17115
17529
|
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
17116
17530
|
return failure2("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
17117
17531
|
return failure2("recovery_required", "snapshot does not satisfy its schema");
|
|
17118
17532
|
}
|
|
17119
|
-
processStart(pid) {
|
|
17120
|
-
try {
|
|
17121
|
-
return fs4.readFileSync(`/proc/${pid}/stat`, "utf8").split(" ")[21] ?? null;
|
|
17122
|
-
} catch {
|
|
17123
|
-
return null;
|
|
17124
|
-
}
|
|
17125
|
-
}
|
|
17126
17533
|
conflict(expected, actual) {
|
|
17127
17534
|
return failure2("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
17128
17535
|
expectedRevision: expected,
|
|
17129
17536
|
actualRevision: actual
|
|
17130
17537
|
});
|
|
17131
17538
|
}
|
|
17539
|
+
workspaceConflict(expected, actual) {
|
|
17540
|
+
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
17541
|
+
}
|
|
17132
17542
|
taskPath(taskId) {
|
|
17133
17543
|
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
17134
17544
|
}
|
|
@@ -17154,53 +17564,116 @@ class TaskStore {
|
|
|
17154
17564
|
return path4.join(this.workitDir, ".gitignore");
|
|
17155
17565
|
}
|
|
17156
17566
|
}
|
|
17157
|
-
// packages/workit-core/src/core/
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
// packages/workit-core/src/core/auto-approval.ts
|
|
17166
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
17167
|
-
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
17168
|
-
import path8 from "node:path";
|
|
17169
|
-
|
|
17170
|
-
// packages/workit-core/src/core/workspaces.ts
|
|
17171
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
17172
|
-
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
17173
|
-
import path6 from "node:path";
|
|
17174
|
-
|
|
17175
|
-
// packages/workit-core/src/core/config.ts
|
|
17176
|
-
import {
|
|
17177
|
-
copyFileSync,
|
|
17178
|
-
cpSync,
|
|
17179
|
-
existsSync as existsSync3,
|
|
17180
|
-
mkdirSync as mkdirSync2,
|
|
17181
|
-
readFileSync as readFileSync2,
|
|
17182
|
-
readdirSync as readdirSync2,
|
|
17183
|
-
writeFileSync as writeFileSync2
|
|
17184
|
-
} from "node:fs";
|
|
17185
|
-
import os from "node:os";
|
|
17186
|
-
import path5 from "node:path";
|
|
17187
|
-
|
|
17188
|
-
// packages/workit-core/src/core/boundary.ts
|
|
17189
|
-
import { statSync as statSync2 } from "node:fs";
|
|
17190
|
-
var EVENT = {
|
|
17191
|
-
initialization: "initialization",
|
|
17192
|
-
provenance: "provenance",
|
|
17193
|
-
assets: "assets",
|
|
17194
|
-
configurationSource: "configuration_source",
|
|
17195
|
-
hooks: "hooks",
|
|
17196
|
-
mcpConnection: "mcp_connection",
|
|
17197
|
-
migration: "migration",
|
|
17198
|
-
installSteps: "install_steps",
|
|
17199
|
-
uncaughtFailure: "uncaught_failure",
|
|
17200
|
-
toolsFailed: "tools_failed",
|
|
17201
|
-
doctor: "doctor"
|
|
17567
|
+
// packages/workit-core/src/core/workers.ts
|
|
17568
|
+
var same = (left, right) => {
|
|
17569
|
+
try {
|
|
17570
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
17571
|
+
} catch {
|
|
17572
|
+
return false;
|
|
17573
|
+
}
|
|
17202
17574
|
};
|
|
17203
|
-
var
|
|
17575
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
17576
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
17577
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
17578
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
17579
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
17580
|
+
const code = char.charCodeAt(0);
|
|
17581
|
+
return code < 32 || code === 127;
|
|
17582
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
17583
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
17584
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
17585
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
17586
|
+
const owner = workspace.writer?.owner;
|
|
17587
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
17588
|
+
};
|
|
17589
|
+
function assertProductWriteAllowed(input) {
|
|
17590
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
17591
|
+
return failure2("invalid_input", "invalid product write path");
|
|
17592
|
+
if (!input.store)
|
|
17593
|
+
return failure2("permission_denied", "write authorization requires core state");
|
|
17594
|
+
const task = input.store.readTask(input.task.id);
|
|
17595
|
+
if (!task.ok)
|
|
17596
|
+
return task;
|
|
17597
|
+
const workspace = input.store.readWorkspace();
|
|
17598
|
+
if (!workspace.ok)
|
|
17599
|
+
return workspace;
|
|
17600
|
+
if (!workspace.data)
|
|
17601
|
+
return failure2("not_found", "workspace not found");
|
|
17602
|
+
const workspaceRecord = workspace.data;
|
|
17603
|
+
if (task.data.status !== "active")
|
|
17604
|
+
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
17605
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
17606
|
+
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
17607
|
+
const writer = workspaceRecord.writer;
|
|
17608
|
+
if (!writer)
|
|
17609
|
+
return failure2("permission_denied", "checkout has no writer owner");
|
|
17610
|
+
if (writer.state === "uncertain")
|
|
17611
|
+
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
17612
|
+
owner: writer.owner
|
|
17613
|
+
});
|
|
17614
|
+
const caller = callerValue(input.caller);
|
|
17615
|
+
const workerId = workerIdOf(input.caller);
|
|
17616
|
+
const owner = writer.owner;
|
|
17617
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
17618
|
+
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
17619
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
17620
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
17621
|
+
return failure2("permission_denied", "only implementers can own product writes");
|
|
17622
|
+
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))))
|
|
17623
|
+
return failure2("recovery_required", "worker session is not an active writer");
|
|
17624
|
+
return success(null, workspaceRecord.revision, owner);
|
|
17625
|
+
}
|
|
17626
|
+
|
|
17627
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
17628
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
17629
|
+
import * as fs8 from "node:fs";
|
|
17630
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
17631
|
+
import path10 from "node:path";
|
|
17632
|
+
|
|
17633
|
+
// packages/workit-core/src/core/authority.ts
|
|
17634
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
17635
|
+
import * as fs7 from "node:fs";
|
|
17636
|
+
import path9 from "node:path";
|
|
17637
|
+
|
|
17638
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
17639
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
17640
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
17641
|
+
import path8 from "node:path";
|
|
17642
|
+
|
|
17643
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
17644
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
17645
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
17646
|
+
import path6 from "node:path";
|
|
17647
|
+
|
|
17648
|
+
// packages/workit-core/src/core/config.ts
|
|
17649
|
+
import {
|
|
17650
|
+
copyFileSync,
|
|
17651
|
+
cpSync,
|
|
17652
|
+
existsSync as existsSync3,
|
|
17653
|
+
mkdirSync as mkdirSync2,
|
|
17654
|
+
readFileSync as readFileSync2,
|
|
17655
|
+
readdirSync as readdirSync2,
|
|
17656
|
+
writeFileSync as writeFileSync2
|
|
17657
|
+
} from "node:fs";
|
|
17658
|
+
import os from "node:os";
|
|
17659
|
+
import path5 from "node:path";
|
|
17660
|
+
|
|
17661
|
+
// packages/workit-core/src/core/boundary.ts
|
|
17662
|
+
import { statSync as statSync2 } from "node:fs";
|
|
17663
|
+
var EVENT = {
|
|
17664
|
+
initialization: "initialization",
|
|
17665
|
+
provenance: "provenance",
|
|
17666
|
+
assets: "assets",
|
|
17667
|
+
configurationSource: "configuration_source",
|
|
17668
|
+
hooks: "hooks",
|
|
17669
|
+
mcpConnection: "mcp_connection",
|
|
17670
|
+
migration: "migration",
|
|
17671
|
+
installSteps: "install_steps",
|
|
17672
|
+
uncaughtFailure: "uncaught_failure",
|
|
17673
|
+
toolsFailed: "tools_failed",
|
|
17674
|
+
doctor: "doctor"
|
|
17675
|
+
};
|
|
17676
|
+
var errorDetail = (err) => {
|
|
17204
17677
|
if (err instanceof Error) {
|
|
17205
17678
|
return { error_name: err.name, error: err.message };
|
|
17206
17679
|
}
|
|
@@ -17829,13 +18302,13 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
17829
18302
|
};
|
|
17830
18303
|
|
|
17831
18304
|
// packages/workit-core/src/core/vcs-config.ts
|
|
17832
|
-
import
|
|
18305
|
+
import fs6 from "node:fs";
|
|
17833
18306
|
import path7 from "node:path";
|
|
17834
|
-
import { spawnSync } from "node:child_process";
|
|
18307
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17835
18308
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
17836
18309
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
17837
18310
|
var remoteProvider = (cwd) => {
|
|
17838
|
-
const r =
|
|
18311
|
+
const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
17839
18312
|
if (r.status !== 0)
|
|
17840
18313
|
return null;
|
|
17841
18314
|
const url = (r.stdout ?? "").trim();
|
|
@@ -17872,7 +18345,7 @@ var remoteEndpoint = (remote) => {
|
|
|
17872
18345
|
var configuredSshEndpoint = (host) => {
|
|
17873
18346
|
if (host.startsWith("-"))
|
|
17874
18347
|
return null;
|
|
17875
|
-
const resolved =
|
|
18348
|
+
const resolved = spawnSync2("ssh", ["-G", host], {
|
|
17876
18349
|
encoding: "utf8",
|
|
17877
18350
|
timeout: 5000,
|
|
17878
18351
|
maxBuffer: 1024 * 1024,
|
|
@@ -17922,7 +18395,7 @@ var readVcsConfig = () => {
|
|
|
17922
18395
|
const file = vcsConfigPath();
|
|
17923
18396
|
let raw;
|
|
17924
18397
|
try {
|
|
17925
|
-
raw =
|
|
18398
|
+
raw = fs6.readFileSync(file, "utf8");
|
|
17926
18399
|
} catch {
|
|
17927
18400
|
return { status: "missing", path: file, config: {} };
|
|
17928
18401
|
}
|
|
@@ -18023,7 +18496,7 @@ function vcsCliIdentity(cwd) {
|
|
|
18023
18496
|
PATH: process.env.PATH ?? "",
|
|
18024
18497
|
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
18025
18498
|
};
|
|
18026
|
-
const result =
|
|
18499
|
+
const result = spawnSync2(bin, ["api", "user"], {
|
|
18027
18500
|
cwd: root,
|
|
18028
18501
|
encoding: "utf8",
|
|
18029
18502
|
env
|
|
@@ -18065,7 +18538,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18065
18538
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
18066
18539
|
descriptionPreview: desc.slice(0, 600)
|
|
18067
18540
|
});
|
|
18068
|
-
const remoteResult =
|
|
18541
|
+
const remoteResult = spawnSync2("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
18069
18542
|
cwd,
|
|
18070
18543
|
encoding: "utf8"
|
|
18071
18544
|
});
|
|
@@ -18081,7 +18554,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18081
18554
|
if (!hostingApiHostMatches(remote, host))
|
|
18082
18555
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
18083
18556
|
if (provider === "gitlab") {
|
|
18084
|
-
const run = (args) =>
|
|
18557
|
+
const run = (args) => spawnSync2("glab", ["api", ...args], {
|
|
18085
18558
|
cwd,
|
|
18086
18559
|
encoding: "utf8",
|
|
18087
18560
|
env
|
|
@@ -18101,7 +18574,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18101
18574
|
});
|
|
18102
18575
|
}
|
|
18103
18576
|
} else if (provider === "github") {
|
|
18104
|
-
const r =
|
|
18577
|
+
const r = spawnSync2("gh", [
|
|
18105
18578
|
"pr",
|
|
18106
18579
|
"list",
|
|
18107
18580
|
"--repo",
|
|
@@ -18160,66 +18633,6 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
18160
18633
|
return { ok: true };
|
|
18161
18634
|
};
|
|
18162
18635
|
|
|
18163
|
-
// packages/workit-core/src/core/workers.ts
|
|
18164
|
-
var same = (left, right) => {
|
|
18165
|
-
try {
|
|
18166
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
18167
|
-
} catch {
|
|
18168
|
-
return false;
|
|
18169
|
-
}
|
|
18170
|
-
};
|
|
18171
|
-
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
18172
|
-
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
18173
|
-
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
18174
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
18175
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
18176
|
-
const code = char.charCodeAt(0);
|
|
18177
|
-
return code < 32 || code === 127;
|
|
18178
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
18179
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
18180
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
18181
|
-
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
18182
|
-
const owner = workspace.writer?.owner;
|
|
18183
|
-
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
18184
|
-
};
|
|
18185
|
-
function assertProductWriteAllowed(input) {
|
|
18186
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
18187
|
-
return failure2("invalid_input", "invalid product write path");
|
|
18188
|
-
if (!input.store)
|
|
18189
|
-
return failure2("permission_denied", "write authorization requires core state");
|
|
18190
|
-
const task = input.store.readTask(input.task.id);
|
|
18191
|
-
if (!task.ok)
|
|
18192
|
-
return task;
|
|
18193
|
-
const workspace = input.store.readWorkspace();
|
|
18194
|
-
if (!workspace.ok)
|
|
18195
|
-
return workspace;
|
|
18196
|
-
if (!workspace.data)
|
|
18197
|
-
return failure2("not_found", "workspace not found");
|
|
18198
|
-
const workspaceRecord = workspace.data;
|
|
18199
|
-
if (task.data.status !== "active")
|
|
18200
|
-
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
18201
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
18202
|
-
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
18203
|
-
const writer = workspaceRecord.writer;
|
|
18204
|
-
if (!writer)
|
|
18205
|
-
return failure2("permission_denied", "checkout has no writer owner");
|
|
18206
|
-
if (writer.state === "uncertain")
|
|
18207
|
-
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
18208
|
-
owner: writer.owner
|
|
18209
|
-
});
|
|
18210
|
-
const caller = callerValue(input.caller);
|
|
18211
|
-
const workerId = workerIdOf(input.caller);
|
|
18212
|
-
const owner = writer.owner;
|
|
18213
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
18214
|
-
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
18215
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
18216
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
18217
|
-
return failure2("permission_denied", "only implementers can own product writes");
|
|
18218
|
-
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))))
|
|
18219
|
-
return failure2("recovery_required", "worker session is not an active writer");
|
|
18220
|
-
return success(null, workspaceRecord.revision, owner);
|
|
18221
|
-
}
|
|
18222
|
-
|
|
18223
18636
|
// packages/workit-core/src/core/auto-approval.ts
|
|
18224
18637
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
18225
18638
|
var OPERATION_CLASS = {
|
|
@@ -18506,7 +18919,7 @@ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
|
18506
18919
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
18507
18920
|
let root;
|
|
18508
18921
|
try {
|
|
18509
|
-
root =
|
|
18922
|
+
root = fs7.realpathSync(checkoutRoot);
|
|
18510
18923
|
} catch {
|
|
18511
18924
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
18512
18925
|
}
|
|
@@ -18529,8 +18942,8 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
18529
18942
|
if (!segment)
|
|
18530
18943
|
continue;
|
|
18531
18944
|
current = path9.join(current, segment);
|
|
18532
|
-
const stat =
|
|
18533
|
-
const real =
|
|
18945
|
+
const stat = fs7.lstatSync(current);
|
|
18946
|
+
const real = fs7.realpathSync(current);
|
|
18534
18947
|
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
18535
18948
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
18536
18949
|
fields: [
|
|
@@ -18547,7 +18960,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
18547
18960
|
if (current !== target && !stat.isDirectory())
|
|
18548
18961
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
18549
18962
|
}
|
|
18550
|
-
if (digestBytes2(
|
|
18963
|
+
if (digestBytes2(fs7.readFileSync(target)) !== reference.digest)
|
|
18551
18964
|
return failure2("permission_denied", "approved document bytes have changed");
|
|
18552
18965
|
} catch {
|
|
18553
18966
|
return failure2("invalid_input", "approved document is unavailable");
|
|
@@ -18910,18 +19323,41 @@ function reconcileAction(input) {
|
|
|
18910
19323
|
var bindingCovers = scopeCovers;
|
|
18911
19324
|
var verifyDecisionContent = verifyContentRefs;
|
|
18912
19325
|
|
|
18913
|
-
// packages/workit-core/src/core/
|
|
18914
|
-
var
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
19326
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
19327
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
19328
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
19329
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
19330
|
+
var canonicalPath = (value) => {
|
|
19331
|
+
if (typeof value !== "string" || !value)
|
|
19332
|
+
return null;
|
|
19333
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
19334
|
+
return null;
|
|
19335
|
+
const normalized = path10.posix.normalize(value);
|
|
19336
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
19337
|
+
return null;
|
|
19338
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
19339
|
+
};
|
|
19340
|
+
var canonicalScope = (scope) => {
|
|
19341
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
19342
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
19343
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
19344
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
19345
|
+
return null;
|
|
19346
|
+
return {
|
|
19347
|
+
description: scope.description,
|
|
19348
|
+
paths: [...new Set(normalizedPaths)],
|
|
19349
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
19350
|
+
};
|
|
19351
|
+
};
|
|
19352
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
19353
|
+
var scopeMatches = (value, scope) => {
|
|
19354
|
+
const normalized = canonicalScope(scope);
|
|
19355
|
+
if (!normalized)
|
|
19356
|
+
return false;
|
|
19357
|
+
const paths = normalized.paths;
|
|
19358
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
19359
|
+
};
|
|
19360
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
18925
19361
|
var compareCodeUnits2 = (left, right) => {
|
|
18926
19362
|
const length = Math.min(left.length, right.length);
|
|
18927
19363
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -18931,362 +19367,8 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
18931
19367
|
}
|
|
18932
19368
|
return left.length - right.length;
|
|
18933
19369
|
};
|
|
18934
|
-
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits2);
|
|
18935
|
-
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
|
|
18936
|
-
var normalizedScope = (scope) => ({
|
|
18937
|
-
description: scope.description,
|
|
18938
|
-
paths: stableUnique(scope.paths),
|
|
18939
|
-
exclusions: stableUnique(scope.exclusions)
|
|
18940
|
-
});
|
|
18941
|
-
var normalizedRefList = (values) => stableList(values);
|
|
18942
|
-
var normalizeAssessment = (assessment) => ({
|
|
18943
|
-
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
18944
|
-
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
18945
|
-
name,
|
|
18946
|
-
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
18947
|
-
])),
|
|
18948
|
-
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
18949
|
-
...consequence,
|
|
18950
|
-
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
18951
|
-
}))),
|
|
18952
|
-
verification: stableList(assessment.verification.map((verification) => ({
|
|
18953
|
-
...verification,
|
|
18954
|
-
scope: normalizedScope(verification.scope),
|
|
18955
|
-
availableChecks: normalizedRefList(verification.availableChecks),
|
|
18956
|
-
gaps: stableUnique(verification.gaps)
|
|
18957
|
-
})))
|
|
18958
|
-
});
|
|
18959
|
-
var normalizeConstraint = (constraint) => ({
|
|
18960
|
-
...constraint,
|
|
18961
|
-
source: constraint.source,
|
|
18962
|
-
requires: stableList(constraint.requires.map((requirement) => ({
|
|
18963
|
-
...requirement,
|
|
18964
|
-
scope: normalizedScope(requirement.scope),
|
|
18965
|
-
refs: normalizedRefList(requirement.refs)
|
|
18966
|
-
})))
|
|
18967
|
-
});
|
|
18968
|
-
var normalizeDecision = (decision) => ({
|
|
18969
|
-
...decision,
|
|
18970
|
-
binding: {
|
|
18971
|
-
...decision.binding,
|
|
18972
|
-
scope: normalizedScope(decision.binding.scope),
|
|
18973
|
-
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
18974
|
-
},
|
|
18975
|
-
requirementIds: stableUnique(decision.requirementIds)
|
|
18976
|
-
});
|
|
18977
|
-
var normalizeIntent = (intent) => ({
|
|
18978
|
-
...intent,
|
|
18979
|
-
scope: normalizedScope(intent.scope),
|
|
18980
|
-
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
18981
|
-
});
|
|
18982
|
-
var normalizePrior = (prior) => ({
|
|
18983
|
-
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
18984
|
-
findings: stableList(prior.findings.map((finding) => ({
|
|
18985
|
-
...finding,
|
|
18986
|
-
scope: normalizedScope(finding.scope),
|
|
18987
|
-
refs: normalizedRefList(finding.refs),
|
|
18988
|
-
resolution: finding.resolution ? {
|
|
18989
|
-
...finding.resolution,
|
|
18990
|
-
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
18991
|
-
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
18992
|
-
} : null
|
|
18993
|
-
}))),
|
|
18994
|
-
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
18995
|
-
...requirement,
|
|
18996
|
-
scope: normalizedScope(requirement.scope)
|
|
18997
|
-
})))
|
|
18998
|
-
});
|
|
18999
|
-
var invalidFields = (error) => ({
|
|
19000
|
-
fields: error.issues.map((issue) => ({
|
|
19001
|
-
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
19002
|
-
reason: issue.message
|
|
19003
|
-
}))
|
|
19004
|
-
});
|
|
19005
|
-
function normalizeResolverInput(input) {
|
|
19006
|
-
const parsed = resolverInputSchema.safeParse(input);
|
|
19007
|
-
if (!parsed.success)
|
|
19008
|
-
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
19009
|
-
const value = parsed.data;
|
|
19010
|
-
const signals = value.assessment.signals;
|
|
19011
|
-
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
19012
|
-
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
19013
|
-
fields: [
|
|
19014
|
-
{
|
|
19015
|
-
path: "assessment.signals",
|
|
19016
|
-
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
19017
|
-
}
|
|
19018
|
-
]
|
|
19019
|
-
});
|
|
19020
|
-
const ids = value.constraints.map((constraint) => constraint.id);
|
|
19021
|
-
if (new Set(ids).size !== ids.length)
|
|
19022
|
-
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
19023
|
-
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
19024
|
-
});
|
|
19025
|
-
if (value.preferences?.fast && value.preferences.thorough)
|
|
19026
|
-
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
19027
|
-
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
19028
|
-
});
|
|
19029
|
-
const normalized = {
|
|
19030
|
-
intent: normalizeIntent(value.intent),
|
|
19031
|
-
assessment: normalizeAssessment(value.assessment),
|
|
19032
|
-
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
19033
|
-
prior: normalizePrior(value.prior),
|
|
19034
|
-
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
19035
|
-
policyVersion: POLICY_VERSION
|
|
19036
|
-
};
|
|
19037
|
-
return success(null, null, normalized);
|
|
19038
|
-
}
|
|
19039
|
-
var requirement = (spec) => ({
|
|
19040
|
-
...spec,
|
|
19041
|
-
id: requirementId({
|
|
19042
|
-
ruleId: spec.ruleId,
|
|
19043
|
-
scope: spec.scope,
|
|
19044
|
-
satisfaction: spec.satisfaction,
|
|
19045
|
-
before: spec.before,
|
|
19046
|
-
dependentAction: spec.dependentAction
|
|
19047
|
-
})
|
|
19048
|
-
});
|
|
19049
|
-
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
19050
|
-
function resolveRequirements(input) {
|
|
19051
|
-
const { signals } = input.assessment;
|
|
19052
|
-
const scope = input.intent.scope;
|
|
19053
|
-
const requirements = [];
|
|
19054
|
-
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
19055
|
-
const hasUnknownFact = [
|
|
19056
|
-
...input.assessment.facts,
|
|
19057
|
-
...input.assessment.consequences.map(({ fact }) => fact)
|
|
19058
|
-
].some((fact) => fact.basis === "unknown");
|
|
19059
|
-
if (hasUnknownSignal || hasUnknownFact)
|
|
19060
|
-
requirements.push(requirement({
|
|
19061
|
-
ruleId: "consequential-unknown",
|
|
19062
|
-
dimension: "investigation",
|
|
19063
|
-
scope,
|
|
19064
|
-
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
19065
|
-
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
19066
|
-
...hasUnknownFact ? ["unknown fact"] : []
|
|
19067
|
-
].join(", ")}`,
|
|
19068
|
-
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
19069
|
-
before: "dependent_action",
|
|
19070
|
-
dependentAction: null,
|
|
19071
|
-
acceptanceAllowed: false
|
|
19072
|
-
}));
|
|
19073
|
-
if (signals.productChoiceOpen.value === true)
|
|
19074
|
-
requirements.push(requirement({
|
|
19075
|
-
ruleId: "product-decision",
|
|
19076
|
-
dimension: "decisions",
|
|
19077
|
-
scope,
|
|
19078
|
-
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
19079
|
-
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
19080
|
-
before: "dependent_action",
|
|
19081
|
-
dependentAction: null,
|
|
19082
|
-
acceptanceAllowed: false
|
|
19083
|
-
}));
|
|
19084
|
-
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
19085
|
-
if (signals.behaviorChange.value === true)
|
|
19086
|
-
requirements.push(requirement({
|
|
19087
|
-
ruleId: "behavioral-verification",
|
|
19088
|
-
dimension: "testing",
|
|
19089
|
-
scope,
|
|
19090
|
-
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
19091
|
-
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
19092
|
-
before: "close",
|
|
19093
|
-
dependentAction: null,
|
|
19094
|
-
acceptanceAllowed: false
|
|
19095
|
-
}), requirement({
|
|
19096
|
-
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
19097
|
-
dimension: "review",
|
|
19098
|
-
scope,
|
|
19099
|
-
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.",
|
|
19100
|
-
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.",
|
|
19101
|
-
before: "close",
|
|
19102
|
-
dependentAction: null,
|
|
19103
|
-
acceptanceAllowed: !independentBehaviorReview
|
|
19104
|
-
}));
|
|
19105
|
-
else if (signals.mechanicalLowRisk.value === true)
|
|
19106
|
-
requirements.push(requirement({
|
|
19107
|
-
ruleId: "mechanical-existing-checks",
|
|
19108
|
-
dimension: "verification",
|
|
19109
|
-
scope,
|
|
19110
|
-
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
19111
|
-
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
19112
|
-
before: "close",
|
|
19113
|
-
dependentAction: null,
|
|
19114
|
-
acceptanceAllowed: true
|
|
19115
|
-
}), requirement({
|
|
19116
|
-
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
19117
|
-
dimension: "review",
|
|
19118
|
-
scope,
|
|
19119
|
-
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.",
|
|
19120
|
-
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.",
|
|
19121
|
-
before: "close",
|
|
19122
|
-
dependentAction: null,
|
|
19123
|
-
acceptanceAllowed: !input.preferences?.thorough
|
|
19124
|
-
}));
|
|
19125
|
-
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
19126
|
-
requirements.push(requirement({
|
|
19127
|
-
ruleId: "pre-pr-cleanup",
|
|
19128
|
-
dimension: "verification",
|
|
19129
|
-
scope,
|
|
19130
|
-
reason: "Implementation output must be deslopped before it is delivered.",
|
|
19131
|
-
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
19132
|
-
before: "dependent_action",
|
|
19133
|
-
dependentAction: "hosting.pull_request",
|
|
19134
|
-
acceptanceAllowed: true
|
|
19135
|
-
}));
|
|
19136
|
-
if (signals.durableAgreementNeeded.value === true)
|
|
19137
|
-
requirements.push(requirement({
|
|
19138
|
-
ruleId: "durable-spec",
|
|
19139
|
-
dimension: "artifacts",
|
|
19140
|
-
scope,
|
|
19141
|
-
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
19142
|
-
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
19143
|
-
before: "write",
|
|
19144
|
-
dependentAction: null,
|
|
19145
|
-
acceptanceAllowed: true
|
|
19146
|
-
}));
|
|
19147
|
-
if (signals.coordinationPlanNeeded.value === true)
|
|
19148
|
-
requirements.push(requirement({
|
|
19149
|
-
ruleId: "coordination-plan",
|
|
19150
|
-
dimension: "continuity",
|
|
19151
|
-
scope,
|
|
19152
|
-
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
19153
|
-
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
19154
|
-
before: "dependent_action",
|
|
19155
|
-
dependentAction: null,
|
|
19156
|
-
acceptanceAllowed: true
|
|
19157
|
-
}));
|
|
19158
|
-
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
19159
|
-
requirements.push(requirement({
|
|
19160
|
-
ruleId: "helper-usefulness",
|
|
19161
|
-
dimension: "delegation",
|
|
19162
|
-
scope,
|
|
19163
|
-
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
19164
|
-
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
19165
|
-
before: "dependent_action",
|
|
19166
|
-
dependentAction: null,
|
|
19167
|
-
acceptanceAllowed: true
|
|
19168
|
-
}));
|
|
19169
|
-
for (const constraint of input.constraints)
|
|
19170
|
-
for (const obligation of constraint.requires) {
|
|
19171
|
-
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
19172
|
-
requirements.push(requirement({
|
|
19173
|
-
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
19174
|
-
kind: obligation.kind,
|
|
19175
|
-
scope: normalizedScope(obligation.scope),
|
|
19176
|
-
refs: normalizedRefList(obligation.refs),
|
|
19177
|
-
method: obligation.method,
|
|
19178
|
-
before: obligation.before,
|
|
19179
|
-
dependentAction: obligation.dependentAction
|
|
19180
|
-
})}`,
|
|
19181
|
-
dimension,
|
|
19182
|
-
scope: obligation.scope,
|
|
19183
|
-
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
19184
|
-
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
19185
|
-
before: obligation.before,
|
|
19186
|
-
dependentAction: obligation.dependentAction,
|
|
19187
|
-
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
19188
|
-
}));
|
|
19189
|
-
}
|
|
19190
|
-
const seen = new Set;
|
|
19191
|
-
return requirements.filter((item) => {
|
|
19192
|
-
if (seen.has(item.id))
|
|
19193
|
-
return false;
|
|
19194
|
-
seen.add(item.id);
|
|
19195
|
-
return true;
|
|
19196
|
-
});
|
|
19197
|
-
}
|
|
19198
|
-
function resolvePolicy(input) {
|
|
19199
|
-
const normalized = normalizeResolverInput(input);
|
|
19200
|
-
if (!normalized.ok)
|
|
19201
|
-
return normalized;
|
|
19202
|
-
const requirements = resolveRequirements(normalized.data);
|
|
19203
|
-
const policy = {
|
|
19204
|
-
policyVersion: POLICY_VERSION,
|
|
19205
|
-
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
19206
|
-
requirements
|
|
19207
|
-
};
|
|
19208
|
-
const parsed = policySchema.safeParse(policy);
|
|
19209
|
-
if (!parsed.success)
|
|
19210
|
-
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
19211
|
-
return success(null, null, parsed.data);
|
|
19212
|
-
}
|
|
19213
|
-
function diffPolicy(previous, next, reason, now) {
|
|
19214
|
-
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
19215
|
-
throw new TypeError("previous policy is invalid");
|
|
19216
|
-
if (!policySchema.safeParse(next).success)
|
|
19217
|
-
throw new TypeError("next policy is invalid");
|
|
19218
|
-
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
19219
|
-
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
19220
|
-
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits2);
|
|
19221
|
-
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits2);
|
|
19222
|
-
if (added.length === 0 && retired.length === 0)
|
|
19223
|
-
return null;
|
|
19224
|
-
const normalizedReason = reason.trim();
|
|
19225
|
-
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
19226
|
-
const change = {
|
|
19227
|
-
recordedAt: now,
|
|
19228
|
-
fromInputDigest: previous?.inputDigest ?? null,
|
|
19229
|
-
toInputDigest: next.inputDigest,
|
|
19230
|
-
added,
|
|
19231
|
-
retired,
|
|
19232
|
-
reason: changeReason
|
|
19233
|
-
};
|
|
19234
|
-
const parsed = policyChangeSchema.safeParse(change);
|
|
19235
|
-
if (!parsed.success)
|
|
19236
|
-
throw new TypeError("policy change is invalid");
|
|
19237
|
-
return parsed.data;
|
|
19238
|
-
}
|
|
19239
|
-
|
|
19240
|
-
// packages/workit-core/src/core/task-evaluation.ts
|
|
19241
|
-
import { createHash as createHash6 } from "node:crypto";
|
|
19242
|
-
import * as fs7 from "node:fs";
|
|
19243
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
19244
|
-
import path10 from "node:path";
|
|
19245
|
-
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
19246
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
19247
|
-
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
19248
|
-
var canonicalPath = (value) => {
|
|
19249
|
-
if (typeof value !== "string" || !value)
|
|
19250
|
-
return null;
|
|
19251
|
-
if (value.includes("\\") || value.split("/").includes(".."))
|
|
19252
|
-
return null;
|
|
19253
|
-
const normalized = path10.posix.normalize(value);
|
|
19254
|
-
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
19255
|
-
return null;
|
|
19256
|
-
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
19257
|
-
};
|
|
19258
|
-
var canonicalScope = (scope) => {
|
|
19259
|
-
const paths = scope.paths.length ? scope.paths : ["."];
|
|
19260
|
-
const normalizedPaths = paths.map(canonicalPath);
|
|
19261
|
-
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
19262
|
-
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
19263
|
-
return null;
|
|
19264
|
-
return {
|
|
19265
|
-
description: scope.description,
|
|
19266
|
-
paths: [...new Set(normalizedPaths)],
|
|
19267
|
-
exclusions: [...new Set(normalizedExclusions)]
|
|
19268
|
-
};
|
|
19269
|
-
};
|
|
19270
|
-
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
19271
|
-
var scopeMatches = (value, scope) => {
|
|
19272
|
-
const normalized = canonicalScope(scope);
|
|
19273
|
-
if (!normalized)
|
|
19274
|
-
return false;
|
|
19275
|
-
const paths = normalized.paths;
|
|
19276
|
-
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
19277
|
-
};
|
|
19278
|
-
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
19279
|
-
var compareCodeUnits3 = (left, right) => {
|
|
19280
|
-
const length = Math.min(left.length, right.length);
|
|
19281
|
-
for (let index = 0;index < length; index += 1) {
|
|
19282
|
-
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
19283
|
-
if (difference !== 0)
|
|
19284
|
-
return difference;
|
|
19285
|
-
}
|
|
19286
|
-
return left.length - right.length;
|
|
19287
|
-
};
|
|
19288
19370
|
var gitPaths = (root) => {
|
|
19289
|
-
const result =
|
|
19371
|
+
const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19290
19372
|
cwd: root,
|
|
19291
19373
|
encoding: "buffer"
|
|
19292
19374
|
});
|
|
@@ -19299,7 +19381,7 @@ var gitPaths = (root) => {
|
|
|
19299
19381
|
};
|
|
19300
19382
|
}
|
|
19301
19383
|
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
19302
|
-
const stagedDeleted =
|
|
19384
|
+
const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
19303
19385
|
if (stagedDeleted.status !== 0) {
|
|
19304
19386
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
19305
19387
|
if (!/not a git repository/i.test(stderr))
|
|
@@ -19312,7 +19394,7 @@ var gitPaths = (root) => {
|
|
|
19312
19394
|
var walk = (root, directory, output) => {
|
|
19313
19395
|
let names;
|
|
19314
19396
|
try {
|
|
19315
|
-
names =
|
|
19397
|
+
names = fs8.readdirSync(directory);
|
|
19316
19398
|
} catch {
|
|
19317
19399
|
return true;
|
|
19318
19400
|
}
|
|
@@ -19323,7 +19405,7 @@ var walk = (root, directory, output) => {
|
|
|
19323
19405
|
const target = path10.join(directory, name);
|
|
19324
19406
|
let stat;
|
|
19325
19407
|
try {
|
|
19326
|
-
stat =
|
|
19408
|
+
stat = fs8.lstatSync(target);
|
|
19327
19409
|
} catch {
|
|
19328
19410
|
uncertain = true;
|
|
19329
19411
|
continue;
|
|
@@ -19342,10 +19424,10 @@ var scopeRoots = (root, scope) => {
|
|
|
19342
19424
|
if (!inside(root, target))
|
|
19343
19425
|
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` }] });
|
|
19344
19426
|
let ancestor = target;
|
|
19345
|
-
while (!
|
|
19427
|
+
while (!fs8.existsSync(ancestor) && ancestor !== root)
|
|
19346
19428
|
ancestor = path10.dirname(ancestor);
|
|
19347
19429
|
try {
|
|
19348
|
-
if (!inside(root,
|
|
19430
|
+
if (!inside(root, fs8.realpathSync(ancestor)))
|
|
19349
19431
|
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` }] });
|
|
19350
19432
|
} catch {
|
|
19351
19433
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -19357,7 +19439,7 @@ var scopeRoots = (root, scope) => {
|
|
|
19357
19439
|
function captureCandidate(root, scope, environment = []) {
|
|
19358
19440
|
let checkout;
|
|
19359
19441
|
try {
|
|
19360
|
-
checkout =
|
|
19442
|
+
checkout = fs8.realpathSync(root);
|
|
19361
19443
|
} catch {
|
|
19362
19444
|
return failure2("invalid_input", "candidate root does not exist");
|
|
19363
19445
|
}
|
|
@@ -19375,11 +19457,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19375
19457
|
if (item === ".git" || item === ".workit")
|
|
19376
19458
|
continue;
|
|
19377
19459
|
names.add(item);
|
|
19378
|
-
if (!
|
|
19460
|
+
if (!fs8.existsSync(target))
|
|
19379
19461
|
continue;
|
|
19380
19462
|
let stat;
|
|
19381
19463
|
try {
|
|
19382
|
-
stat =
|
|
19464
|
+
stat = fs8.lstatSync(target);
|
|
19383
19465
|
} catch {
|
|
19384
19466
|
uncertain = true;
|
|
19385
19467
|
continue;
|
|
@@ -19397,15 +19479,15 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19397
19479
|
continue;
|
|
19398
19480
|
const target = path10.join(checkout, item);
|
|
19399
19481
|
try {
|
|
19400
|
-
const stat =
|
|
19482
|
+
const stat = fs8.lstatSync(target);
|
|
19401
19483
|
if (stat.isSymbolicLink()) {
|
|
19402
|
-
const link =
|
|
19484
|
+
const link = fs8.readlinkSync(target);
|
|
19403
19485
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
19404
19486
|
} else if (stat.isFile()) {
|
|
19405
19487
|
files.push({
|
|
19406
19488
|
path: item,
|
|
19407
19489
|
kind: "file",
|
|
19408
|
-
digest: digestBytes3(
|
|
19490
|
+
digest: digestBytes3(fs8.readFileSync(target)),
|
|
19409
19491
|
executable: (stat.mode & 73) !== 0
|
|
19410
19492
|
});
|
|
19411
19493
|
} else if (!stat.isDirectory()) {
|
|
@@ -19427,10 +19509,10 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19427
19509
|
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
19428
19510
|
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
19429
19511
|
return failure2("invalid_input", "candidate environment names must be unique and non-empty");
|
|
19430
|
-
const values = supplied.toSorted((left, right) =>
|
|
19512
|
+
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
19431
19513
|
if (values.some(({ value }) => value === null))
|
|
19432
19514
|
completeness = "uncertain";
|
|
19433
|
-
const headResult =
|
|
19515
|
+
const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
19434
19516
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
19435
19517
|
completeness = "uncertain";
|
|
19436
19518
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -19735,166 +19817,608 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
19735
19817
|
});
|
|
19736
19818
|
}
|
|
19737
19819
|
|
|
19738
|
-
// packages/workit-core/src/core/methods.ts
|
|
19739
|
-
var METHODS = {
|
|
19740
|
-
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
19741
|
-
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
19742
|
-
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
19743
|
-
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
19744
|
-
"workit-implement": { dimensions: ["delegation"] },
|
|
19745
|
-
"workit-debug": {},
|
|
19746
|
-
"workit-handoff": {},
|
|
19747
|
-
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
19748
|
-
};
|
|
19749
|
-
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
19750
|
-
var assuranceFor = (id, definition, capabilities) => {
|
|
19751
|
-
const keys = new Set([
|
|
19752
|
-
id,
|
|
19753
|
-
...definition.dimensions ?? [],
|
|
19754
|
-
...definition.ruleIds ?? []
|
|
19755
|
-
]);
|
|
19756
|
-
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
19757
|
-
if (id === "workit-review" && matches.length === 0)
|
|
19758
|
-
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
19759
|
-
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
19760
|
-
return {
|
|
19761
|
-
assurance: "unavailable",
|
|
19762
|
-
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
19763
|
-
};
|
|
19764
|
-
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
19765
|
-
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
19766
|
-
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
19820
|
+
// packages/workit-core/src/core/methods.ts
|
|
19821
|
+
var METHODS = {
|
|
19822
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
19823
|
+
"workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
|
|
19824
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
|
|
19825
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
19826
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
19827
|
+
"workit-debug": {},
|
|
19828
|
+
"workit-handoff": {},
|
|
19829
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
19830
|
+
};
|
|
19831
|
+
var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
19832
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
19833
|
+
const keys = new Set([
|
|
19834
|
+
id,
|
|
19835
|
+
...definition.dimensions ?? [],
|
|
19836
|
+
...definition.ruleIds ?? []
|
|
19837
|
+
]);
|
|
19838
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
19839
|
+
if (id === "workit-review" && matches.length === 0)
|
|
19840
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
19841
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
19842
|
+
return {
|
|
19843
|
+
assurance: "unavailable",
|
|
19844
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
19845
|
+
};
|
|
19846
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
19847
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
19848
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
19849
|
+
};
|
|
19850
|
+
function selectMethods(policy, capabilities) {
|
|
19851
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
19852
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
19853
|
+
if (requirements.length === 0)
|
|
19854
|
+
return [];
|
|
19855
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
19856
|
+
const reasons = [
|
|
19857
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
19858
|
+
...capability.reasons
|
|
19859
|
+
];
|
|
19860
|
+
if (capability.assurance === "unavailable")
|
|
19861
|
+
reasons.unshift("required capability is unavailable");
|
|
19862
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
19863
|
+
});
|
|
19864
|
+
}
|
|
19865
|
+
|
|
19866
|
+
// packages/workit-core/src/core/task-context.ts
|
|
19867
|
+
function reconcileResume(view, observations = []) {
|
|
19868
|
+
if (observations.length > 0)
|
|
19869
|
+
return failure2("permission_denied", "worker observations require native host verification");
|
|
19870
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
19871
|
+
if (!candidate.ok)
|
|
19872
|
+
return candidate;
|
|
19873
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
19874
|
+
const blockers = [...view.task.progress.blockers];
|
|
19875
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
19876
|
+
blockers.push({
|
|
19877
|
+
reason: "worker state requires reconciliation",
|
|
19878
|
+
dependentAction: "resume",
|
|
19879
|
+
refs: []
|
|
19880
|
+
});
|
|
19881
|
+
return success(null, null, {
|
|
19882
|
+
candidate: candidate.data,
|
|
19883
|
+
staleEvidenceIds,
|
|
19884
|
+
workerUpdates: [],
|
|
19885
|
+
blockers,
|
|
19886
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
19887
|
+
});
|
|
19888
|
+
}
|
|
19889
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
19890
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
19891
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
19892
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
19893
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
19894
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
19895
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
19896
|
+
const separator = match.search(/[:=]/u);
|
|
19897
|
+
if (separator >= 0)
|
|
19898
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
19899
|
+
const space = match.search(/\s/u);
|
|
19900
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
19901
|
+
}).slice(0, limit);
|
|
19902
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
19903
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
19904
|
+
var compactDecisionChoice = (entry) => {
|
|
19905
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
19906
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
19907
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
19908
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
19909
|
+
};
|
|
19910
|
+
var compactReference = (ref) => {
|
|
19911
|
+
if (ref.kind === "file")
|
|
19912
|
+
return {
|
|
19913
|
+
kind: "file",
|
|
19914
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
19915
|
+
};
|
|
19916
|
+
if (ref.kind === "record")
|
|
19917
|
+
return {
|
|
19918
|
+
kind: "record",
|
|
19919
|
+
collection: ref.collection,
|
|
19920
|
+
id: ref.id
|
|
19921
|
+
};
|
|
19922
|
+
return { kind: ref.kind };
|
|
19923
|
+
};
|
|
19924
|
+
function compactTaskContext(view) {
|
|
19925
|
+
const decisions = view.task.decisions.slice().toSorted((left, right) => {
|
|
19926
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
19927
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
19928
|
+
if (leftAt !== rightAt)
|
|
19929
|
+
return leftAt > rightAt ? -1 : 1;
|
|
19930
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
19931
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
19932
|
+
id: entry.id,
|
|
19933
|
+
purpose: entry.data.purpose,
|
|
19934
|
+
status: entry.data.response,
|
|
19935
|
+
digest: entry.data.digest,
|
|
19936
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
19937
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
19938
|
+
}));
|
|
19939
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
19940
|
+
id: method.id,
|
|
19941
|
+
assurance: method.assurance,
|
|
19942
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
19943
|
+
})) : [];
|
|
19944
|
+
const gaps = Array.from(new Set([
|
|
19945
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
19946
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
19947
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
19948
|
+
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
19949
|
+
const context = {
|
|
19950
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
19951
|
+
status: view.task.status,
|
|
19952
|
+
decisions,
|
|
19953
|
+
methods,
|
|
19954
|
+
gaps,
|
|
19955
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
19956
|
+
};
|
|
19957
|
+
let encoded = JSON.stringify(context);
|
|
19958
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
19959
|
+
if (context.gaps.length)
|
|
19960
|
+
context.gaps.pop();
|
|
19961
|
+
else if (context.decisions.length)
|
|
19962
|
+
context.decisions.pop();
|
|
19963
|
+
else
|
|
19964
|
+
context.methods.pop();
|
|
19965
|
+
encoded = JSON.stringify(context);
|
|
19966
|
+
}
|
|
19967
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
19968
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
19969
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
19970
|
+
encoded = JSON.stringify(context);
|
|
19971
|
+
}
|
|
19972
|
+
return encoded;
|
|
19973
|
+
}
|
|
19974
|
+
var exportDigest = (bundle) => sha256({
|
|
19975
|
+
schemaVersion: bundle.schemaVersion,
|
|
19976
|
+
exportedAt: bundle.exportedAt,
|
|
19977
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
19978
|
+
task: bundle.task
|
|
19979
|
+
});
|
|
19980
|
+
// packages/workit-core/src/core/external-action.ts
|
|
19981
|
+
var chainStepSchema = union([
|
|
19982
|
+
string2().min(1),
|
|
19983
|
+
object({ branch: string2().min(1) }).strict(),
|
|
19984
|
+
object({ pr: literal(true) }).strict()
|
|
19985
|
+
]);
|
|
19986
|
+
var externalActionSchema = discriminatedUnion("operation", [
|
|
19987
|
+
object({
|
|
19988
|
+
operation: literal("git.branch_setup"),
|
|
19989
|
+
payload: object({
|
|
19990
|
+
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
19991
|
+
sdd_dir: string2().optional(),
|
|
19992
|
+
target_branch: string2().optional(),
|
|
19993
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
19994
|
+
cwd: string2().min(1).optional()
|
|
19995
|
+
}).strict()
|
|
19996
|
+
}).strict(),
|
|
19997
|
+
object({
|
|
19998
|
+
operation: literal("git.commit"),
|
|
19999
|
+
payload: object({
|
|
20000
|
+
message: string2().min(1).optional(),
|
|
20001
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
20002
|
+
plan_branch: string2().min(1).optional(),
|
|
20003
|
+
cwd: string2().min(1).optional()
|
|
20004
|
+
}).strict()
|
|
20005
|
+
}).strict(),
|
|
20006
|
+
object({
|
|
20007
|
+
operation: literal("git.push"),
|
|
20008
|
+
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
20009
|
+
}).strict(),
|
|
20010
|
+
object({
|
|
20011
|
+
operation: literal("hosting.pull_request"),
|
|
20012
|
+
payload: object({
|
|
20013
|
+
title: string2().min(1),
|
|
20014
|
+
body: string2().optional(),
|
|
20015
|
+
draft: boolean2().optional(),
|
|
20016
|
+
target_branch: string2().optional(),
|
|
20017
|
+
babysit: boolean2().optional(),
|
|
20018
|
+
cwd: string2().min(1).optional()
|
|
20019
|
+
}).strict()
|
|
20020
|
+
}).strict(),
|
|
20021
|
+
object({
|
|
20022
|
+
operation: literal("hosting.merge"),
|
|
20023
|
+
payload: object({
|
|
20024
|
+
target_branch: string2().min(1).optional(),
|
|
20025
|
+
source_branch: string2().min(1).optional(),
|
|
20026
|
+
cwd: string2().min(1).optional()
|
|
20027
|
+
}).strict()
|
|
20028
|
+
}).strict(),
|
|
20029
|
+
object({
|
|
20030
|
+
operation: literal("hosting.delete_branch"),
|
|
20031
|
+
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
20032
|
+
}).strict(),
|
|
20033
|
+
object({
|
|
20034
|
+
operation: literal("youtrack.update"),
|
|
20035
|
+
payload: object({
|
|
20036
|
+
issueId: string2().min(1),
|
|
20037
|
+
markdown: string2().min(1),
|
|
20038
|
+
minutes: number2().positive().optional()
|
|
20039
|
+
}).strict()
|
|
20040
|
+
}).strict(),
|
|
20041
|
+
object({
|
|
20042
|
+
operation: literal("youtrack.time"),
|
|
20043
|
+
payload: object({
|
|
20044
|
+
issueId: string2().min(1),
|
|
20045
|
+
minutes: number2().positive(),
|
|
20046
|
+
text: string2().optional(),
|
|
20047
|
+
dateMs: number2().finite().optional()
|
|
20048
|
+
}).strict()
|
|
20049
|
+
}).strict(),
|
|
20050
|
+
object({
|
|
20051
|
+
operation: literal("youtrack.meeting"),
|
|
20052
|
+
payload: object({
|
|
20053
|
+
issueId: string2().min(1),
|
|
20054
|
+
minutes: number2().positive(),
|
|
20055
|
+
text: string2().min(1)
|
|
20056
|
+
}).strict()
|
|
20057
|
+
}).strict(),
|
|
20058
|
+
object({
|
|
20059
|
+
operation: literal("changelog.apply"),
|
|
20060
|
+
payload: object({
|
|
20061
|
+
entries: union([
|
|
20062
|
+
record(string2(), array(string2())),
|
|
20063
|
+
array(object({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
20064
|
+
]).optional(),
|
|
20065
|
+
path: string2().optional(),
|
|
20066
|
+
normalize_only: boolean2().optional()
|
|
20067
|
+
}).strict()
|
|
20068
|
+
}).strict(),
|
|
20069
|
+
object({
|
|
20070
|
+
operation: literal("context.read"),
|
|
20071
|
+
payload: object({
|
|
20072
|
+
kind: _enum([
|
|
20073
|
+
"git",
|
|
20074
|
+
"pr",
|
|
20075
|
+
"youtrack",
|
|
20076
|
+
"github_issue",
|
|
20077
|
+
"gitlab_issue",
|
|
20078
|
+
"changelog",
|
|
20079
|
+
"release",
|
|
20080
|
+
"affected"
|
|
20081
|
+
]),
|
|
20082
|
+
range: string2().optional(),
|
|
20083
|
+
issueId: string2().optional(),
|
|
20084
|
+
issueUrl: string2().optional(),
|
|
20085
|
+
issueRef: string2().optional(),
|
|
20086
|
+
mode: string2().optional(),
|
|
20087
|
+
specPath: string2().optional(),
|
|
20088
|
+
planPath: string2().optional(),
|
|
20089
|
+
cwd: string2().min(1).optional()
|
|
20090
|
+
}).strict()
|
|
20091
|
+
}).strict()
|
|
20092
|
+
]);
|
|
20093
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
20094
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
20095
|
+
|
|
20096
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
20097
|
+
var resolverInputSchema = object({
|
|
20098
|
+
intent: intentSchema,
|
|
20099
|
+
assessment: assessmentSchema,
|
|
20100
|
+
constraints: array(constraintSchema),
|
|
20101
|
+
prior: object({
|
|
20102
|
+
decisions: array(decisionSchema),
|
|
20103
|
+
findings: array(findingSchema),
|
|
20104
|
+
requirements: array(requirementSchema)
|
|
20105
|
+
}).strict(),
|
|
20106
|
+
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
20107
|
+
}).strict();
|
|
20108
|
+
var compareCodeUnits3 = (left, right) => {
|
|
20109
|
+
const length = Math.min(left.length, right.length);
|
|
20110
|
+
for (let index = 0;index < length; index += 1) {
|
|
20111
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
20112
|
+
if (difference !== 0)
|
|
20113
|
+
return difference;
|
|
20114
|
+
}
|
|
20115
|
+
return left.length - right.length;
|
|
19767
20116
|
};
|
|
19768
|
-
|
|
19769
|
-
|
|
19770
|
-
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19782
|
-
|
|
19783
|
-
|
|
19784
|
-
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
20117
|
+
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
20118
|
+
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
20119
|
+
var normalizedScope = (scope) => ({
|
|
20120
|
+
description: scope.description,
|
|
20121
|
+
paths: stableUnique(scope.paths),
|
|
20122
|
+
exclusions: stableUnique(scope.exclusions)
|
|
20123
|
+
});
|
|
20124
|
+
var normalizedRefList = (values) => stableList(values);
|
|
20125
|
+
var normalizeAssessment = (assessment) => ({
|
|
20126
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
20127
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
20128
|
+
name,
|
|
20129
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
20130
|
+
])),
|
|
20131
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
20132
|
+
...consequence,
|
|
20133
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
20134
|
+
}))),
|
|
20135
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
20136
|
+
...verification,
|
|
20137
|
+
scope: normalizedScope(verification.scope),
|
|
20138
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
20139
|
+
gaps: stableUnique(verification.gaps)
|
|
20140
|
+
})))
|
|
20141
|
+
});
|
|
20142
|
+
var normalizeConstraint = (constraint) => ({
|
|
20143
|
+
...constraint,
|
|
20144
|
+
source: constraint.source,
|
|
20145
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
20146
|
+
...requirement,
|
|
20147
|
+
scope: normalizedScope(requirement.scope),
|
|
20148
|
+
refs: normalizedRefList(requirement.refs)
|
|
20149
|
+
})))
|
|
20150
|
+
});
|
|
20151
|
+
var normalizeDecision = (decision) => ({
|
|
20152
|
+
...decision,
|
|
20153
|
+
binding: {
|
|
20154
|
+
...decision.binding,
|
|
20155
|
+
scope: normalizedScope(decision.binding.scope),
|
|
20156
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
20157
|
+
},
|
|
20158
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
20159
|
+
});
|
|
20160
|
+
var normalizeIntent = (intent) => ({
|
|
20161
|
+
...intent,
|
|
20162
|
+
scope: normalizedScope(intent.scope),
|
|
20163
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
20164
|
+
});
|
|
20165
|
+
var normalizePrior = (prior) => ({
|
|
20166
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
20167
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
20168
|
+
...finding,
|
|
20169
|
+
scope: normalizedScope(finding.scope),
|
|
20170
|
+
refs: normalizedRefList(finding.refs),
|
|
20171
|
+
resolution: finding.resolution ? {
|
|
20172
|
+
...finding.resolution,
|
|
20173
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
20174
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
20175
|
+
} : null
|
|
20176
|
+
}))),
|
|
20177
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
20178
|
+
...requirement,
|
|
20179
|
+
scope: normalizedScope(requirement.scope)
|
|
20180
|
+
})))
|
|
20181
|
+
});
|
|
20182
|
+
var invalidFields = (error) => ({
|
|
20183
|
+
fields: error.issues.map((issue) => ({
|
|
20184
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
20185
|
+
reason: issue.message
|
|
20186
|
+
}))
|
|
20187
|
+
});
|
|
20188
|
+
function normalizeResolverInput(input) {
|
|
20189
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
20190
|
+
if (!parsed.success)
|
|
20191
|
+
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
20192
|
+
const value = parsed.data;
|
|
20193
|
+
const signals = value.assessment.signals;
|
|
20194
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
20195
|
+
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
20196
|
+
fields: [
|
|
20197
|
+
{
|
|
20198
|
+
path: "assessment.signals",
|
|
20199
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
20200
|
+
}
|
|
20201
|
+
]
|
|
19798
20202
|
});
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
20203
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
20204
|
+
if (new Set(ids).size !== ids.length)
|
|
20205
|
+
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
20206
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
20207
|
+
});
|
|
20208
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
20209
|
+
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
20210
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
20211
|
+
});
|
|
20212
|
+
const normalized = {
|
|
20213
|
+
intent: normalizeIntent(value.intent),
|
|
20214
|
+
assessment: normalizeAssessment(value.assessment),
|
|
20215
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
20216
|
+
prior: normalizePrior(value.prior),
|
|
20217
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
20218
|
+
policyVersion: POLICY_VERSION
|
|
20219
|
+
};
|
|
20220
|
+
return success(null, null, normalized);
|
|
19806
20221
|
}
|
|
19807
|
-
var
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19813
|
-
|
|
19814
|
-
|
|
19815
|
-
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
}
|
|
19820
|
-
|
|
19821
|
-
|
|
19822
|
-
|
|
19823
|
-
const
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
|
|
19831
|
-
|
|
19832
|
-
|
|
19833
|
-
|
|
19834
|
-
|
|
19835
|
-
|
|
19836
|
-
|
|
19837
|
-
|
|
19838
|
-
|
|
19839
|
-
|
|
19840
|
-
|
|
19841
|
-
|
|
19842
|
-
|
|
19843
|
-
|
|
19844
|
-
|
|
19845
|
-
|
|
19846
|
-
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
|
|
19851
|
-
|
|
19852
|
-
|
|
19853
|
-
|
|
19854
|
-
|
|
19855
|
-
|
|
19856
|
-
|
|
19857
|
-
|
|
19858
|
-
|
|
19859
|
-
|
|
19860
|
-
|
|
19861
|
-
|
|
19862
|
-
|
|
19863
|
-
|
|
19864
|
-
|
|
19865
|
-
|
|
19866
|
-
|
|
19867
|
-
|
|
19868
|
-
|
|
19869
|
-
|
|
19870
|
-
|
|
19871
|
-
|
|
19872
|
-
|
|
19873
|
-
|
|
20222
|
+
var requirement = (spec) => ({
|
|
20223
|
+
...spec,
|
|
20224
|
+
id: requirementId({
|
|
20225
|
+
ruleId: spec.ruleId,
|
|
20226
|
+
scope: spec.scope,
|
|
20227
|
+
satisfaction: spec.satisfaction,
|
|
20228
|
+
before: spec.before,
|
|
20229
|
+
dependentAction: spec.dependentAction
|
|
20230
|
+
})
|
|
20231
|
+
});
|
|
20232
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
20233
|
+
function resolveRequirements(input) {
|
|
20234
|
+
const { signals } = input.assessment;
|
|
20235
|
+
const scope = input.intent.scope;
|
|
20236
|
+
const requirements = [];
|
|
20237
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
20238
|
+
const hasUnknownFact = [
|
|
20239
|
+
...input.assessment.facts,
|
|
20240
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
20241
|
+
].some((fact) => fact.basis === "unknown");
|
|
20242
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
20243
|
+
requirements.push(requirement({
|
|
20244
|
+
ruleId: "consequential-unknown",
|
|
20245
|
+
dimension: "investigation",
|
|
20246
|
+
scope,
|
|
20247
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
20248
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
20249
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
20250
|
+
].join(", ")}`,
|
|
20251
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
20252
|
+
before: "dependent_action",
|
|
20253
|
+
dependentAction: null,
|
|
20254
|
+
acceptanceAllowed: false
|
|
20255
|
+
}));
|
|
20256
|
+
if (signals.productChoiceOpen.value === true)
|
|
20257
|
+
requirements.push(requirement({
|
|
20258
|
+
ruleId: "product-decision",
|
|
20259
|
+
dimension: "decisions",
|
|
20260
|
+
scope,
|
|
20261
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
20262
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
20263
|
+
before: "dependent_action",
|
|
20264
|
+
dependentAction: null,
|
|
20265
|
+
acceptanceAllowed: false
|
|
20266
|
+
}));
|
|
20267
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
20268
|
+
if (signals.behaviorChange.value === true)
|
|
20269
|
+
requirements.push(requirement({
|
|
20270
|
+
ruleId: "behavioral-verification",
|
|
20271
|
+
dimension: "testing",
|
|
20272
|
+
scope,
|
|
20273
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
20274
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
20275
|
+
before: "close",
|
|
20276
|
+
dependentAction: null,
|
|
20277
|
+
acceptanceAllowed: false
|
|
20278
|
+
}), requirement({
|
|
20279
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
20280
|
+
dimension: "review",
|
|
20281
|
+
scope,
|
|
20282
|
+
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.",
|
|
20283
|
+
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.",
|
|
20284
|
+
before: "close",
|
|
20285
|
+
dependentAction: null,
|
|
20286
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
20287
|
+
}));
|
|
20288
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
20289
|
+
requirements.push(requirement({
|
|
20290
|
+
ruleId: "mechanical-existing-checks",
|
|
20291
|
+
dimension: "verification",
|
|
20292
|
+
scope,
|
|
20293
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
20294
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
20295
|
+
before: "close",
|
|
20296
|
+
dependentAction: null,
|
|
20297
|
+
acceptanceAllowed: true
|
|
20298
|
+
}), requirement({
|
|
20299
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
20300
|
+
dimension: "review",
|
|
20301
|
+
scope,
|
|
20302
|
+
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.",
|
|
20303
|
+
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.",
|
|
20304
|
+
before: "close",
|
|
20305
|
+
dependentAction: null,
|
|
20306
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
20307
|
+
}));
|
|
20308
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
20309
|
+
requirements.push(requirement({
|
|
20310
|
+
ruleId: "pre-pr-cleanup",
|
|
20311
|
+
dimension: "verification",
|
|
20312
|
+
scope,
|
|
20313
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
20314
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
20315
|
+
before: "dependent_action",
|
|
20316
|
+
dependentAction: "hosting.pull_request",
|
|
20317
|
+
acceptanceAllowed: true
|
|
20318
|
+
}));
|
|
20319
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
20320
|
+
requirements.push(requirement({
|
|
20321
|
+
ruleId: "durable-spec",
|
|
20322
|
+
dimension: "artifacts",
|
|
20323
|
+
scope,
|
|
20324
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
20325
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
20326
|
+
before: "write",
|
|
20327
|
+
dependentAction: null,
|
|
20328
|
+
acceptanceAllowed: true
|
|
20329
|
+
}));
|
|
20330
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
20331
|
+
requirements.push(requirement({
|
|
20332
|
+
ruleId: "coordination-plan",
|
|
20333
|
+
dimension: "continuity",
|
|
20334
|
+
scope,
|
|
20335
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
20336
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
20337
|
+
before: "dependent_action",
|
|
20338
|
+
dependentAction: null,
|
|
20339
|
+
acceptanceAllowed: true
|
|
20340
|
+
}));
|
|
20341
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
20342
|
+
requirements.push(requirement({
|
|
20343
|
+
ruleId: "helper-usefulness",
|
|
20344
|
+
dimension: "delegation",
|
|
20345
|
+
scope,
|
|
20346
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
20347
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
20348
|
+
before: "dependent_action",
|
|
20349
|
+
dependentAction: null,
|
|
20350
|
+
acceptanceAllowed: true
|
|
20351
|
+
}));
|
|
20352
|
+
for (const constraint of input.constraints)
|
|
20353
|
+
for (const obligation of constraint.requires) {
|
|
20354
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
20355
|
+
requirements.push(requirement({
|
|
20356
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
20357
|
+
kind: obligation.kind,
|
|
20358
|
+
scope: normalizedScope(obligation.scope),
|
|
20359
|
+
refs: normalizedRefList(obligation.refs),
|
|
20360
|
+
method: obligation.method,
|
|
20361
|
+
before: obligation.before,
|
|
20362
|
+
dependentAction: obligation.dependentAction
|
|
20363
|
+
})}`,
|
|
20364
|
+
dimension,
|
|
20365
|
+
scope: obligation.scope,
|
|
20366
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
20367
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
20368
|
+
before: obligation.before,
|
|
20369
|
+
dependentAction: obligation.dependentAction,
|
|
20370
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
20371
|
+
}));
|
|
20372
|
+
}
|
|
20373
|
+
const seen = new Set;
|
|
20374
|
+
return requirements.filter((item) => {
|
|
20375
|
+
if (seen.has(item.id))
|
|
20376
|
+
return false;
|
|
20377
|
+
seen.add(item.id);
|
|
20378
|
+
return true;
|
|
20379
|
+
});
|
|
20380
|
+
}
|
|
20381
|
+
function resolvePolicy(input) {
|
|
20382
|
+
const normalized = normalizeResolverInput(input);
|
|
20383
|
+
if (!normalized.ok)
|
|
20384
|
+
return normalized;
|
|
20385
|
+
const requirements = resolveRequirements(normalized.data);
|
|
20386
|
+
const policy = {
|
|
20387
|
+
policyVersion: POLICY_VERSION,
|
|
20388
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
20389
|
+
requirements
|
|
19874
20390
|
};
|
|
19875
|
-
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
20391
|
+
const parsed = policySchema.safeParse(policy);
|
|
20392
|
+
if (!parsed.success)
|
|
20393
|
+
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
20394
|
+
return success(null, null, parsed.data);
|
|
20395
|
+
}
|
|
20396
|
+
function diffPolicy(previous, next, reason, now) {
|
|
20397
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
20398
|
+
throw new TypeError("previous policy is invalid");
|
|
20399
|
+
if (!policySchema.safeParse(next).success)
|
|
20400
|
+
throw new TypeError("next policy is invalid");
|
|
20401
|
+
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
20402
|
+
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
20403
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits3);
|
|
20404
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits3);
|
|
20405
|
+
if (added.length === 0 && retired.length === 0)
|
|
20406
|
+
return null;
|
|
20407
|
+
const normalizedReason = reason.trim();
|
|
20408
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
20409
|
+
const change = {
|
|
20410
|
+
recordedAt: now,
|
|
20411
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
20412
|
+
toInputDigest: next.inputDigest,
|
|
20413
|
+
added,
|
|
20414
|
+
retired,
|
|
20415
|
+
reason: changeReason
|
|
20416
|
+
};
|
|
20417
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
20418
|
+
if (!parsed.success)
|
|
20419
|
+
throw new TypeError("policy change is invalid");
|
|
20420
|
+
return parsed.data;
|
|
19891
20421
|
}
|
|
19892
|
-
var exportDigest = (bundle) => sha256({
|
|
19893
|
-
schemaVersion: bundle.schemaVersion,
|
|
19894
|
-
exportedAt: bundle.exportedAt,
|
|
19895
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
19896
|
-
task: bundle.task
|
|
19897
|
-
});
|
|
19898
20422
|
|
|
19899
20423
|
// packages/workit-core/src/core/task-engine.ts
|
|
19900
20424
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -20067,6 +20591,29 @@ var applyWorkerLifecycle = (input) => {
|
|
|
20067
20591
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
|
|
20068
20592
|
};
|
|
20069
20593
|
var refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || bindingCovers(scope, { description: "", paths: [ref.path], exclusions: [] }));
|
|
20594
|
+
var REVISION_RETRY_ATTEMPTS = 8;
|
|
20595
|
+
var retryPause = new Int32Array(new SharedArrayBuffer(4));
|
|
20596
|
+
var filledRevisions = (request) => {
|
|
20597
|
+
const value = typeof request === "object" && request !== null ? request : {};
|
|
20598
|
+
return {
|
|
20599
|
+
task: value.expectedRevision === undefined,
|
|
20600
|
+
workspace: value.expectedWorkspaceRevision === undefined
|
|
20601
|
+
};
|
|
20602
|
+
};
|
|
20603
|
+
var filledConflict = (result, filled) => !result.ok && result.code === "revision_conflict" && (("actualRevision" in result.details) && filled.task || ("actualWorkspaceRevision" in result.details) && filled.workspace);
|
|
20604
|
+
var retryFilledRevisions = (filled, run) => {
|
|
20605
|
+
if (!filled.task && !filled.workspace)
|
|
20606
|
+
return run();
|
|
20607
|
+
let result = run();
|
|
20608
|
+
const deadline = Date.now() + defaultLockTimeout();
|
|
20609
|
+
for (let attempt = 1;filledConflict(result, filled); attempt += 1) {
|
|
20610
|
+
if (attempt >= REVISION_RETRY_ATTEMPTS || Date.now() >= deadline)
|
|
20611
|
+
return failure2("busy", "records kept changing under concurrent writers; retry the call");
|
|
20612
|
+
Atomics.wait(retryPause, 0, 0, Math.floor(Math.random() * 4 * attempt) + 1);
|
|
20613
|
+
result = run();
|
|
20614
|
+
}
|
|
20615
|
+
return result;
|
|
20616
|
+
};
|
|
20070
20617
|
var trustedNow = (context) => typeof context.now === "function" ? context.now() : context.now;
|
|
20071
20618
|
var mapRef = (ref, ids) => {
|
|
20072
20619
|
if (ref.kind !== "record")
|
|
@@ -20314,6 +20861,9 @@ class WorkitCore {
|
|
|
20314
20861
|
if (input.expectedWorkspaceRevision === undefined)
|
|
20315
20862
|
input.expectedWorkspaceRevision = workspace ? workspace.revision : null;
|
|
20316
20863
|
}
|
|
20864
|
+
retryOmittedRevisions(request, run) {
|
|
20865
|
+
return retryFilledRevisions(filledRevisions(request), run);
|
|
20866
|
+
}
|
|
20317
20867
|
helperEntry(task, requireSession = false, requireActive = false) {
|
|
20318
20868
|
const workerId = this.context.workerId ?? null;
|
|
20319
20869
|
if (workerId === null)
|
|
@@ -20345,6 +20895,9 @@ class WorkitCore {
|
|
|
20345
20895
|
return null;
|
|
20346
20896
|
}
|
|
20347
20897
|
state(request) {
|
|
20898
|
+
return request?.action === "import" ? this.retryOmittedRevisions(request, () => this.stateOnce(request)) : this.stateOnce(request);
|
|
20899
|
+
}
|
|
20900
|
+
stateOnce(request) {
|
|
20348
20901
|
const root = this.contextRootError();
|
|
20349
20902
|
if (!root.ok)
|
|
20350
20903
|
return root;
|
|
@@ -20446,6 +20999,9 @@ class WorkitCore {
|
|
|
20446
20999
|
});
|
|
20447
21000
|
}
|
|
20448
21001
|
task(request) {
|
|
21002
|
+
return this.retryOmittedRevisions(request, () => this.taskOnce(request));
|
|
21003
|
+
}
|
|
21004
|
+
taskOnce(request) {
|
|
20449
21005
|
const root = this.contextRootError();
|
|
20450
21006
|
if (!root.ok)
|
|
20451
21007
|
return root;
|
|
@@ -20545,6 +21101,9 @@ class WorkitCore {
|
|
|
20545
21101
|
}
|
|
20546
21102
|
}
|
|
20547
21103
|
policy(request) {
|
|
21104
|
+
return this.retryOmittedRevisions(request, () => this.policyOnce(request));
|
|
21105
|
+
}
|
|
21106
|
+
policyOnce(request) {
|
|
20548
21107
|
const root = this.contextRootError();
|
|
20549
21108
|
if (!root.ok)
|
|
20550
21109
|
return root;
|
|
@@ -20591,6 +21150,9 @@ class WorkitCore {
|
|
|
20591
21150
|
return success(changed.data.revision, null, changed.data.policy);
|
|
20592
21151
|
}
|
|
20593
21152
|
evidence(request) {
|
|
21153
|
+
return this.retryOmittedRevisions(request, () => this.evidenceOnce(request));
|
|
21154
|
+
}
|
|
21155
|
+
evidenceOnce(request) {
|
|
20594
21156
|
const root = this.contextRootError();
|
|
20595
21157
|
if (!root.ok)
|
|
20596
21158
|
return root;
|
|
@@ -20675,6 +21237,23 @@ class WorkitCore {
|
|
|
20675
21237
|
observeStandingDecision(request) {
|
|
20676
21238
|
return this.recordDecision(request, undefined, true, true);
|
|
20677
21239
|
}
|
|
21240
|
+
commitTask(taskId, expected, filled, recheck, update) {
|
|
21241
|
+
let revision = expected;
|
|
21242
|
+
let first = true;
|
|
21243
|
+
return retryFilledRevisions({ task: filled, workspace: false }, () => {
|
|
21244
|
+
if (!first) {
|
|
21245
|
+
const fresh = this.store.readTask(taskId);
|
|
21246
|
+
if (!fresh.ok)
|
|
21247
|
+
return fresh;
|
|
21248
|
+
const checked = recheck(fresh.data);
|
|
21249
|
+
if (!checked.ok)
|
|
21250
|
+
return checked;
|
|
21251
|
+
revision = fresh.data.revision;
|
|
21252
|
+
}
|
|
21253
|
+
first = false;
|
|
21254
|
+
return this.store.mutateTask(taskId, revision, update, trustedNow(this.context));
|
|
21255
|
+
});
|
|
21256
|
+
}
|
|
20678
21257
|
recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
|
|
20679
21258
|
const root = this.contextRootError();
|
|
20680
21259
|
if (!root.ok)
|
|
@@ -20699,6 +21278,7 @@ class WorkitCore {
|
|
|
20699
21278
|
return task;
|
|
20700
21279
|
if ((this.context.workerId ?? null) !== null)
|
|
20701
21280
|
return failure2("permission_denied", "helpers cannot record or revoke decisions");
|
|
21281
|
+
const filled = input.expectedRevision === undefined;
|
|
20702
21282
|
this.fillRevisions(input, task.data);
|
|
20703
21283
|
if (input.action === "record") {
|
|
20704
21284
|
if (task.data.status === "closed")
|
|
@@ -20747,7 +21327,20 @@ class WorkitCore {
|
|
|
20747
21327
|
const nativeProvenance = native?.ok ? retireNativeAuthority(native.data) : null;
|
|
20748
21328
|
if (native?.ok && !nativeProvenance)
|
|
20749
21329
|
return failure2("permission_denied", "native decision authority was retired");
|
|
20750
|
-
|
|
21330
|
+
let standingProvenance = standingApproval?.ok === true ? standingApproval.data : null;
|
|
21331
|
+
const recheck = (fresh) => {
|
|
21332
|
+
if (fresh.status === "closed")
|
|
21333
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
21334
|
+
if (!standingProvenance)
|
|
21335
|
+
return success(null, null, null);
|
|
21336
|
+
const again = verifyStandingApproval(this.store.root, fresh, this.context.caller, input.binding);
|
|
21337
|
+
if (again.ok)
|
|
21338
|
+
standingProvenance = again.data;
|
|
21339
|
+
return again;
|
|
21340
|
+
};
|
|
21341
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, recheck, (current, mutation) => {
|
|
21342
|
+
if (current.status === "closed")
|
|
21343
|
+
return failure2("invalid_transition", "closed task cannot record a decision");
|
|
20751
21344
|
const latestContent = verifyDecisionContent(this.store, input.binding);
|
|
20752
21345
|
if (!latestContent.ok)
|
|
20753
21346
|
return latestContent;
|
|
@@ -20759,14 +21352,14 @@ class WorkitCore {
|
|
|
20759
21352
|
const entry = {
|
|
20760
21353
|
id: newId(),
|
|
20761
21354
|
recordedAt: mutation.now,
|
|
20762
|
-
provenance:
|
|
21355
|
+
provenance: standingProvenance ?? nativeProvenance ?? provenance(this.context, "agent_reported"),
|
|
20763
21356
|
data
|
|
20764
21357
|
};
|
|
20765
21358
|
return success(mutation.revision, null, {
|
|
20766
21359
|
...current,
|
|
20767
21360
|
decisions: [...current.decisions, entry]
|
|
20768
21361
|
});
|
|
20769
|
-
}
|
|
21362
|
+
});
|
|
20770
21363
|
if (!changed.ok)
|
|
20771
21364
|
return changed;
|
|
20772
21365
|
return success(changed.data.revision, null, changed.data.decisions.at(-1));
|
|
@@ -20780,7 +21373,7 @@ class WorkitCore {
|
|
|
20780
21373
|
return failure2("invalid_transition", "decision is already revoked");
|
|
20781
21374
|
if (decision.data.consumption)
|
|
20782
21375
|
return failure2("permission_denied", "consumed decision cannot be revoked");
|
|
20783
|
-
const changed = this.
|
|
21376
|
+
const changed = this.commitTask(task.data.id, input.expectedRevision, filled, () => success(null, null, null), (current, mutation) => {
|
|
20784
21377
|
const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
20785
21378
|
if (!entry)
|
|
20786
21379
|
return failure2("not_found", "decision not found");
|
|
@@ -20797,13 +21390,16 @@ class WorkitCore {
|
|
|
20797
21390
|
...current,
|
|
20798
21391
|
decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? updated : candidate)
|
|
20799
21392
|
});
|
|
20800
|
-
}
|
|
21393
|
+
});
|
|
20801
21394
|
if (!changed.ok)
|
|
20802
21395
|
return changed;
|
|
20803
21396
|
const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
|
|
20804
21397
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "decision disappeared");
|
|
20805
21398
|
}
|
|
20806
21399
|
finding(request) {
|
|
21400
|
+
return this.retryOmittedRevisions(request, () => this.findingOnce(request));
|
|
21401
|
+
}
|
|
21402
|
+
findingOnce(request) {
|
|
20807
21403
|
const root = this.contextRootError();
|
|
20808
21404
|
if (!root.ok)
|
|
20809
21405
|
return root;
|
|
@@ -20927,6 +21523,9 @@ class WorkitCore {
|
|
|
20927
21523
|
return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "finding disappeared");
|
|
20928
21524
|
}
|
|
20929
21525
|
worker(request) {
|
|
21526
|
+
return this.retryOmittedRevisions(request, () => this.workerOnce(request));
|
|
21527
|
+
}
|
|
21528
|
+
workerOnce(request) {
|
|
20930
21529
|
const root = this.contextRootError();
|
|
20931
21530
|
if (!root.ok)
|
|
20932
21531
|
return root;
|
|
@@ -21274,6 +21873,9 @@ class WorkitCore {
|
|
|
21274
21873
|
return applied;
|
|
21275
21874
|
}
|
|
21276
21875
|
writer(request) {
|
|
21876
|
+
return this.retryOmittedRevisions(request, () => this.writerOnce(request));
|
|
21877
|
+
}
|
|
21878
|
+
writerOnce(request) {
|
|
21277
21879
|
const root = this.contextRootError();
|
|
21278
21880
|
if (!root.ok)
|
|
21279
21881
|
return root;
|
|
@@ -21876,122 +22478,6 @@ class WorkitCore {
|
|
|
21876
22478
|
return this.summary(changed.data.task);
|
|
21877
22479
|
}
|
|
21878
22480
|
}
|
|
21879
|
-
|
|
21880
|
-
// packages/workit-core/src/core/session-context.ts
|
|
21881
|
-
var cache = new Map;
|
|
21882
|
-
// packages/workit-core/src/core/external-action.ts
|
|
21883
|
-
var chainStepSchema = union([
|
|
21884
|
-
string2().min(1),
|
|
21885
|
-
object({ branch: string2().min(1) }).strict(),
|
|
21886
|
-
object({ pr: literal(true) }).strict()
|
|
21887
|
-
]);
|
|
21888
|
-
var externalActionSchema = discriminatedUnion("operation", [
|
|
21889
|
-
object({
|
|
21890
|
-
operation: literal("git.branch_setup"),
|
|
21891
|
-
payload: object({
|
|
21892
|
-
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
21893
|
-
sdd_dir: string2().optional(),
|
|
21894
|
-
target_branch: string2().optional(),
|
|
21895
|
-
stash: _enum(["yes", "no"]).optional(),
|
|
21896
|
-
cwd: string2().min(1).optional()
|
|
21897
|
-
}).strict()
|
|
21898
|
-
}).strict(),
|
|
21899
|
-
object({
|
|
21900
|
-
operation: literal("git.commit"),
|
|
21901
|
-
payload: object({
|
|
21902
|
-
message: string2().min(1).optional(),
|
|
21903
|
-
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
21904
|
-
plan_branch: string2().min(1).optional(),
|
|
21905
|
-
cwd: string2().min(1).optional()
|
|
21906
|
-
}).strict()
|
|
21907
|
-
}).strict(),
|
|
21908
|
-
object({
|
|
21909
|
-
operation: literal("git.push"),
|
|
21910
|
-
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
21911
|
-
}).strict(),
|
|
21912
|
-
object({
|
|
21913
|
-
operation: literal("hosting.pull_request"),
|
|
21914
|
-
payload: object({
|
|
21915
|
-
title: string2().min(1),
|
|
21916
|
-
body: string2().optional(),
|
|
21917
|
-
draft: boolean2().optional(),
|
|
21918
|
-
target_branch: string2().optional(),
|
|
21919
|
-
babysit: boolean2().optional(),
|
|
21920
|
-
cwd: string2().min(1).optional()
|
|
21921
|
-
}).strict()
|
|
21922
|
-
}).strict(),
|
|
21923
|
-
object({
|
|
21924
|
-
operation: literal("hosting.merge"),
|
|
21925
|
-
payload: object({
|
|
21926
|
-
target_branch: string2().min(1).optional(),
|
|
21927
|
-
source_branch: string2().min(1).optional(),
|
|
21928
|
-
cwd: string2().min(1).optional()
|
|
21929
|
-
}).strict()
|
|
21930
|
-
}).strict(),
|
|
21931
|
-
object({
|
|
21932
|
-
operation: literal("hosting.delete_branch"),
|
|
21933
|
-
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
21934
|
-
}).strict(),
|
|
21935
|
-
object({
|
|
21936
|
-
operation: literal("youtrack.update"),
|
|
21937
|
-
payload: object({
|
|
21938
|
-
issueId: string2().min(1),
|
|
21939
|
-
markdown: string2().min(1),
|
|
21940
|
-
minutes: number2().positive().optional()
|
|
21941
|
-
}).strict()
|
|
21942
|
-
}).strict(),
|
|
21943
|
-
object({
|
|
21944
|
-
operation: literal("youtrack.time"),
|
|
21945
|
-
payload: object({
|
|
21946
|
-
issueId: string2().min(1),
|
|
21947
|
-
minutes: number2().positive(),
|
|
21948
|
-
text: string2().optional(),
|
|
21949
|
-
dateMs: number2().finite().optional()
|
|
21950
|
-
}).strict()
|
|
21951
|
-
}).strict(),
|
|
21952
|
-
object({
|
|
21953
|
-
operation: literal("youtrack.meeting"),
|
|
21954
|
-
payload: object({
|
|
21955
|
-
issueId: string2().min(1),
|
|
21956
|
-
minutes: number2().positive(),
|
|
21957
|
-
text: string2().min(1)
|
|
21958
|
-
}).strict()
|
|
21959
|
-
}).strict(),
|
|
21960
|
-
object({
|
|
21961
|
-
operation: literal("changelog.apply"),
|
|
21962
|
-
payload: object({
|
|
21963
|
-
entries: union([
|
|
21964
|
-
record(string2(), array(string2())),
|
|
21965
|
-
array(object({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
21966
|
-
]).optional(),
|
|
21967
|
-
path: string2().optional(),
|
|
21968
|
-
normalize_only: boolean2().optional()
|
|
21969
|
-
}).strict()
|
|
21970
|
-
}).strict(),
|
|
21971
|
-
object({
|
|
21972
|
-
operation: literal("context.read"),
|
|
21973
|
-
payload: object({
|
|
21974
|
-
kind: _enum([
|
|
21975
|
-
"git",
|
|
21976
|
-
"pr",
|
|
21977
|
-
"youtrack",
|
|
21978
|
-
"github_issue",
|
|
21979
|
-
"gitlab_issue",
|
|
21980
|
-
"changelog",
|
|
21981
|
-
"release",
|
|
21982
|
-
"affected"
|
|
21983
|
-
]),
|
|
21984
|
-
range: string2().optional(),
|
|
21985
|
-
issueId: string2().optional(),
|
|
21986
|
-
issueUrl: string2().optional(),
|
|
21987
|
-
issueRef: string2().optional(),
|
|
21988
|
-
mode: string2().optional(),
|
|
21989
|
-
specPath: string2().optional(),
|
|
21990
|
-
planPath: string2().optional(),
|
|
21991
|
-
cwd: string2().min(1).optional()
|
|
21992
|
-
}).strict()
|
|
21993
|
-
}).strict()
|
|
21994
|
-
]);
|
|
21995
22481
|
// packages/workit-core/src/core.ts
|
|
21996
22482
|
var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
|
|
21997
22483
|
function gitRevisionParts(value) {
|
|
@@ -22035,7 +22521,7 @@ function resolveInside(root, candidate) {
|
|
|
22035
22521
|
}
|
|
22036
22522
|
function run2(root, executable, args, env = {}) {
|
|
22037
22523
|
const cwd = realpathSync7(root);
|
|
22038
|
-
const result =
|
|
22524
|
+
const result = spawnSync4(executable, args, {
|
|
22039
22525
|
cwd,
|
|
22040
22526
|
encoding: "utf8",
|
|
22041
22527
|
env: { ...process.env, ...env }
|
|
@@ -24495,9 +24981,9 @@ var applyPatterns = (value) => {
|
|
|
24495
24981
|
var redactSecrets = (value) => applyPatterns(value);
|
|
24496
24982
|
|
|
24497
24983
|
// packages/workit-core/src/core/pr-create.ts
|
|
24498
|
-
import
|
|
24984
|
+
import fs9 from "node:fs";
|
|
24499
24985
|
import path12 from "node:path";
|
|
24500
|
-
import { spawnSync as
|
|
24986
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24501
24987
|
function parseGhIssue(value) {
|
|
24502
24988
|
const m = /issues\/(\d+)/.exec(value);
|
|
24503
24989
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
@@ -24510,7 +24996,7 @@ function whichOnPath(tool) {
|
|
|
24510
24996
|
for (const name of names) {
|
|
24511
24997
|
const candidate = path12.join(dir, name);
|
|
24512
24998
|
try {
|
|
24513
|
-
|
|
24999
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
24514
25000
|
return candidate;
|
|
24515
25001
|
} catch {}
|
|
24516
25002
|
}
|
|
@@ -24519,7 +25005,7 @@ function whichOnPath(tool) {
|
|
|
24519
25005
|
}
|
|
24520
25006
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
24521
25007
|
var pushRemote = (cwd) => {
|
|
24522
|
-
const result =
|
|
25008
|
+
const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
24523
25009
|
cwd,
|
|
24524
25010
|
encoding: "utf8"
|
|
24525
25011
|
});
|
|
@@ -24530,7 +25016,7 @@ var pushRemote = (cwd) => {
|
|
|
24530
25016
|
};
|
|
24531
25017
|
|
|
24532
25018
|
// packages/workit-core/src/core/repo-context.ts
|
|
24533
|
-
import { spawnSync as
|
|
25019
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
24534
25020
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
24535
25021
|
import path13 from "node:path";
|
|
24536
25022
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
@@ -24559,7 +25045,7 @@ var invalidRange = (cwd) => ({
|
|
|
24559
25045
|
cwd
|
|
24560
25046
|
});
|
|
24561
25047
|
var runGit = (cwd, args) => {
|
|
24562
|
-
const result =
|
|
25048
|
+
const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
|
|
24563
25049
|
return {
|
|
24564
25050
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24565
25051
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24732,7 +25218,7 @@ var documentationFiles = (cwd) => {
|
|
|
24732
25218
|
walk(cwd, 1);
|
|
24733
25219
|
return matches.toSorted().slice(0, 200);
|
|
24734
25220
|
};
|
|
24735
|
-
var
|
|
25221
|
+
var readTrimmed2 = (file, maxLines) => {
|
|
24736
25222
|
if (!existsSync6(file))
|
|
24737
25223
|
return "";
|
|
24738
25224
|
const lines = readFileSync7(file, "utf8").split(`
|
|
@@ -24799,7 +25285,7 @@ ${status.stderr}` : "");
|
|
|
24799
25285
|
stdout += `template_path: ${template}
|
|
24800
25286
|
|
|
24801
25287
|
`;
|
|
24802
|
-
stdout +=
|
|
25288
|
+
stdout += readTrimmed2(path13.join(cwd, template), 220);
|
|
24803
25289
|
} else {
|
|
24804
25290
|
stdout += `template_path: none
|
|
24805
25291
|
|
|
@@ -24871,7 +25357,7 @@ function changelogContext(root, range) {
|
|
|
24871
25357
|
stdout += printSection("Existing CHANGELOG.md");
|
|
24872
25358
|
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
24873
25359
|
if (existsSync6(changelogPath)) {
|
|
24874
|
-
stdout +=
|
|
25360
|
+
stdout += readTrimmed2(changelogPath, 260) + `
|
|
24875
25361
|
`;
|
|
24876
25362
|
} else {
|
|
24877
25363
|
stdout += `CHANGELOG.md not found.
|
|
@@ -24913,7 +25399,7 @@ function docsRefreshContext(root, range) {
|
|
|
24913
25399
|
stdout += printSection("README Preview");
|
|
24914
25400
|
const readme = path13.join(cwd, "README.md");
|
|
24915
25401
|
if (existsSync6(readme)) {
|
|
24916
|
-
stdout +=
|
|
25402
|
+
stdout += readTrimmed2(readme, 220) + `
|
|
24917
25403
|
`;
|
|
24918
25404
|
} else {
|
|
24919
25405
|
stdout += `README.md not found.
|
|
@@ -25000,7 +25486,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25000
25486
|
}
|
|
25001
25487
|
|
|
25002
25488
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
25003
|
-
import { spawnSync as
|
|
25489
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
25004
25490
|
var repoPathFromRemote = (remote) => {
|
|
25005
25491
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
25006
25492
|
if (!rest)
|
|
@@ -25021,7 +25507,7 @@ var defaultCli = (bin, available) => {
|
|
|
25021
25507
|
if (!available)
|
|
25022
25508
|
return null;
|
|
25023
25509
|
return (args, root) => {
|
|
25024
|
-
const out =
|
|
25510
|
+
const out = spawnSync7(bin, args, {
|
|
25025
25511
|
cwd: root,
|
|
25026
25512
|
encoding: "utf8",
|
|
25027
25513
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25099,7 +25585,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25099
25585
|
};
|
|
25100
25586
|
|
|
25101
25587
|
// packages/workit-core/src/core/youtrack.ts
|
|
25102
|
-
import
|
|
25588
|
+
import fs10 from "node:fs";
|
|
25103
25589
|
import path14 from "node:path";
|
|
25104
25590
|
|
|
25105
25591
|
// packages/workit-core/src/core/templates.ts
|
|
@@ -25118,17 +25604,17 @@ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? p
|
|
|
25118
25604
|
var youTrackTokenModeOk = (p) => {
|
|
25119
25605
|
if (process.platform === "win32")
|
|
25120
25606
|
return true;
|
|
25121
|
-
const mode =
|
|
25607
|
+
const mode = fs10.statSync(p).mode & 511;
|
|
25122
25608
|
return mode === 384;
|
|
25123
25609
|
};
|
|
25124
25610
|
function readYouTrackConfig(required) {
|
|
25125
25611
|
const cfgPath = youTrackConfigPath();
|
|
25126
|
-
if (!
|
|
25612
|
+
if (!fs10.existsSync(cfgPath)) {
|
|
25127
25613
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25128
25614
|
}
|
|
25129
25615
|
let parsed;
|
|
25130
25616
|
try {
|
|
25131
|
-
parsed = JSON.parse(
|
|
25617
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25132
25618
|
} catch {
|
|
25133
25619
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25134
25620
|
}
|
|
@@ -25139,11 +25625,11 @@ function readYouTrackConfig(required) {
|
|
|
25139
25625
|
}
|
|
25140
25626
|
var readYouTrackContextConfig = () => {
|
|
25141
25627
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25142
|
-
if (!
|
|
25628
|
+
if (!fs10.existsSync(cfgPath))
|
|
25143
25629
|
return { data: {} };
|
|
25144
25630
|
let parsed;
|
|
25145
25631
|
try {
|
|
25146
|
-
parsed = JSON.parse(
|
|
25632
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25147
25633
|
} catch {
|
|
25148
25634
|
return { error: "YouTrack configuration is unavailable" };
|
|
25149
25635
|
}
|
|
@@ -25183,11 +25669,11 @@ var youTrackToken = () => {
|
|
|
25183
25669
|
return loaded;
|
|
25184
25670
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25185
25671
|
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
25186
|
-
if (!tokenPath || !
|
|
25672
|
+
if (!tokenPath || !fs10.existsSync(tokenPath))
|
|
25187
25673
|
return { error: "missing youtrack.token" };
|
|
25188
25674
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25189
25675
|
return { error: "youtrack.token mode must be 0600" };
|
|
25190
|
-
const token =
|
|
25676
|
+
const token = fs10.readFileSync(tokenPath, "utf8").trim();
|
|
25191
25677
|
if (!token)
|
|
25192
25678
|
return { error: "empty token file" };
|
|
25193
25679
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25252,9 +25738,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25252
25738
|
} catch {
|
|
25253
25739
|
continue;
|
|
25254
25740
|
}
|
|
25255
|
-
if (!
|
|
25741
|
+
if (!fs10.existsSync(full))
|
|
25256
25742
|
continue;
|
|
25257
|
-
const text =
|
|
25743
|
+
const text = fs10.readFileSync(full, "utf8");
|
|
25258
25744
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25259
25745
|
if (m)
|
|
25260
25746
|
return m[1];
|
|
@@ -25797,123 +26283,166 @@ async function runStdioServer(host, contextProvider = defaultContextProvider(hos
|
|
|
25797
26283
|
await server.connect(new StdioServerTransport);
|
|
25798
26284
|
return server;
|
|
25799
26285
|
}
|
|
26286
|
+
// packages/workit-core/src/hooks/descriptor.ts
|
|
26287
|
+
var support = (descriptor, axis) => {
|
|
26288
|
+
if (axis.startsWith("event:"))
|
|
26289
|
+
return descriptor.events[axis.slice("event:".length)].support;
|
|
26290
|
+
if (axis === "shellPolicy.deny")
|
|
26291
|
+
return descriptor.shellPolicy.deny;
|
|
26292
|
+
if (axis === "stopControl" || axis === "shellAvailable")
|
|
26293
|
+
return descriptor[axis];
|
|
26294
|
+
const [group, key] = axis.split(".");
|
|
26295
|
+
return descriptor[group][key];
|
|
26296
|
+
};
|
|
26297
|
+
var usable = (value) => value === "native" || value === "partial";
|
|
26298
|
+
var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map((rule) => {
|
|
26299
|
+
const levels = rule.requires.map((axis) => support(descriptor, axis));
|
|
26300
|
+
const available = rule.assurance !== "unavailable" && levels.every(usable) && (rule.observed ?? []).every((flag) => observed[flag] === true);
|
|
26301
|
+
const assurance = !available ? "unavailable" : rule.assurance === "enforced" && (levels.length === 0 || levels.some((level) => level !== "native")) ? "agent_guided" : rule.assurance;
|
|
26302
|
+
return {
|
|
26303
|
+
name: rule.name,
|
|
26304
|
+
surface: rule.surface,
|
|
26305
|
+
assurance,
|
|
26306
|
+
reason: assurance === "unavailable" ? rule.unavailableReason ?? rule.reason : rule.reason,
|
|
26307
|
+
refs: rule.refs.map((handle) => ({ kind: "host", host: descriptor.host, handle }))
|
|
26308
|
+
};
|
|
26309
|
+
});
|
|
26310
|
+
// packages/workit-core/src/hooks/context.ts
|
|
26311
|
+
var cache = new Map;
|
|
26312
|
+
// packages/workit-core/src/hooks/handle.ts
|
|
26313
|
+
var offered = new Set;
|
|
26314
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
26315
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
25800
26316
|
|
|
25801
|
-
// packages/workit-
|
|
25802
|
-
var
|
|
26317
|
+
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
26318
|
+
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
26319
|
+
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
26320
|
+
var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
|
|
26321
|
+
|
|
26322
|
+
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
25803
26323
|
function detectCodexSurface(env) {
|
|
25804
26324
|
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
|
|
25805
26325
|
}
|
|
25806
|
-
var
|
|
25807
|
-
|
|
25808
|
-
|
|
26326
|
+
var undocumented = { support: "undocumented", native: null };
|
|
26327
|
+
var CODEX_DESCRIPTOR = {
|
|
26328
|
+
host: "codex_cli",
|
|
26329
|
+
label: "Codex",
|
|
26330
|
+
verifiedAgainst: "codex-cli 0.153.4; Codex Desktop 26.901.20858",
|
|
26331
|
+
docs: ["https://developers.openai.com/codex/hooks"],
|
|
26332
|
+
transport: "hook-process",
|
|
26333
|
+
events: {
|
|
26334
|
+
"session.start": { support: "native", native: "SessionStart" },
|
|
26335
|
+
"context.turn": undocumented,
|
|
26336
|
+
"shell.pre": { support: "native", native: "PreToolUse" },
|
|
26337
|
+
"tool.pre": { support: "native", native: "PreToolUse" },
|
|
26338
|
+
"shell.post": undocumented,
|
|
26339
|
+
"subagent.start": { support: "native", native: "SubagentStart" },
|
|
26340
|
+
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
26341
|
+
"prompt.submit": undocumented,
|
|
26342
|
+
"compact.pre": { support: "none", native: null },
|
|
26343
|
+
stop: undocumented
|
|
26344
|
+
},
|
|
26345
|
+
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
26346
|
+
context: {
|
|
26347
|
+
sessionStart: "native",
|
|
26348
|
+
perTurn: "undocumented",
|
|
26349
|
+
afterCompact: "native",
|
|
26350
|
+
task: "single-active"
|
|
26351
|
+
},
|
|
26352
|
+
subagents: {
|
|
26353
|
+
identity: "native",
|
|
26354
|
+
parentBinding: "none",
|
|
26355
|
+
blockStart: "none",
|
|
26356
|
+
worktreeIsolation: "undocumented",
|
|
26357
|
+
maxConcurrency: "undocumented"
|
|
26358
|
+
},
|
|
26359
|
+
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
|
|
26360
|
+
interaction: { questions: "none", writeBoundary: "partial" },
|
|
26361
|
+
stopControl: "undocumented",
|
|
26362
|
+
shellAvailable: "native",
|
|
26363
|
+
perEventCost: "low",
|
|
26364
|
+
capabilities: [
|
|
25809
26365
|
{
|
|
25810
26366
|
name: "interactive_decision",
|
|
25811
26367
|
surface: "Question",
|
|
26368
|
+
refs: ["Question"],
|
|
26369
|
+
requires: [],
|
|
25812
26370
|
assurance: "agent_guided",
|
|
25813
|
-
reason: "Codex hooks expose no native arbitrary-question answer receipt"
|
|
25814
|
-
refs: [ref(host, "Question")]
|
|
26371
|
+
reason: "Codex hooks expose no native arbitrary-question answer receipt"
|
|
25815
26372
|
},
|
|
25816
26373
|
{
|
|
25817
26374
|
name: "known_product_writes",
|
|
25818
26375
|
surface: "PreToolUse",
|
|
26376
|
+
refs: ["PreToolUse"],
|
|
26377
|
+
requires: [],
|
|
25819
26378
|
assurance: "unavailable",
|
|
25820
|
-
reason: "file writes are host-policy; PreToolUse allows write tools"
|
|
25821
|
-
refs: [ref(host, "PreToolUse")]
|
|
26379
|
+
reason: "file writes are host-policy; PreToolUse allows write tools"
|
|
25822
26380
|
},
|
|
25823
26381
|
{
|
|
25824
26382
|
name: "fresh-context-review",
|
|
25825
26383
|
surface: "SubagentStart",
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
26384
|
+
refs: ["SubagentStart"],
|
|
26385
|
+
requires: ["event:subagent.start"],
|
|
26386
|
+
observed: ["subagent.start"],
|
|
26387
|
+
assurance: "agent_guided",
|
|
26388
|
+
reason: "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted",
|
|
26389
|
+
unavailableReason: "Codex subagent lifecycle hooks are unavailable for independent review"
|
|
25829
26390
|
},
|
|
25830
26391
|
{
|
|
25831
26392
|
name: "native_subagents",
|
|
25832
26393
|
surface: "SubagentStart/SubagentStop",
|
|
25833
|
-
|
|
25834
|
-
|
|
25835
|
-
|
|
26394
|
+
refs: ["SubagentStart", "SubagentStop"],
|
|
26395
|
+
requires: ["event:subagent.start", "event:subagent.stop"],
|
|
26396
|
+
observed: ["subagent.start", "subagent.stop"],
|
|
26397
|
+
assurance: "agent_guided",
|
|
26398
|
+
reason: "Codex reports stable child identities, but cannot block creation or bind a writer",
|
|
26399
|
+
unavailableReason: "Codex subagent lifecycle hooks are untrusted or incomplete"
|
|
25836
26400
|
},
|
|
25837
26401
|
{
|
|
25838
26402
|
name: "native_subagent_start",
|
|
25839
26403
|
surface: "SubagentStart",
|
|
25840
|
-
|
|
25841
|
-
|
|
25842
|
-
|
|
26404
|
+
refs: ["SubagentStart"],
|
|
26405
|
+
requires: ["event:subagent.start"],
|
|
26406
|
+
observed: ["subagent.start"],
|
|
26407
|
+
assurance: "agent_guided",
|
|
26408
|
+
reason: "SubagentStart supplies identity and bounded read-only guidance; continue:false cannot stop creation",
|
|
26409
|
+
unavailableReason: "SubagentStart hook is untrusted or unavailable"
|
|
25843
26410
|
},
|
|
25844
26411
|
{
|
|
25845
26412
|
name: "arbitrary_shell_write",
|
|
25846
26413
|
surface: "unobservable_shell",
|
|
26414
|
+
refs: ["PreToolUse"],
|
|
26415
|
+
requires: [],
|
|
25847
26416
|
assurance: "unavailable",
|
|
25848
|
-
reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete"
|
|
25849
|
-
refs: [ref(host, "PreToolUse")]
|
|
26417
|
+
reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete"
|
|
25850
26418
|
},
|
|
25851
26419
|
{
|
|
25852
26420
|
name: "compact_context",
|
|
25853
26421
|
surface: "SessionStart",
|
|
25854
|
-
|
|
25855
|
-
|
|
25856
|
-
|
|
26422
|
+
refs: ["SessionStart"],
|
|
26423
|
+
requires: ["context.afterCompact"],
|
|
26424
|
+
observed: ["session.start"],
|
|
26425
|
+
assurance: "agent_guided",
|
|
26426
|
+
reason: "SessionStart source=compact is the single restore path"
|
|
25857
26427
|
}
|
|
25858
|
-
]
|
|
25859
|
-
};
|
|
25860
|
-
var events = new Set([
|
|
25861
|
-
"SessionStart",
|
|
25862
|
-
"PreToolUse",
|
|
25863
|
-
"SubagentStart",
|
|
25864
|
-
"SubagentStop"
|
|
25865
|
-
]);
|
|
25866
|
-
var allowedKeys = {
|
|
25867
|
-
SessionStart: new Set([
|
|
25868
|
-
"hook_event_name",
|
|
25869
|
-
"session_id",
|
|
25870
|
-
"cwd",
|
|
25871
|
-
"model",
|
|
25872
|
-
"permission_mode",
|
|
25873
|
-
"transcript_path",
|
|
25874
|
-
"source"
|
|
25875
|
-
]),
|
|
25876
|
-
PreToolUse: new Set([
|
|
25877
|
-
"hook_event_name",
|
|
25878
|
-
"session_id",
|
|
25879
|
-
"cwd",
|
|
25880
|
-
"model",
|
|
25881
|
-
"permission_mode",
|
|
25882
|
-
"transcript_path",
|
|
25883
|
-
"turn_id",
|
|
25884
|
-
"tool_name",
|
|
25885
|
-
"tool_input",
|
|
25886
|
-
"tool_use_id",
|
|
25887
|
-
"agent_id",
|
|
25888
|
-
"agent_type"
|
|
25889
|
-
]),
|
|
25890
|
-
SubagentStart: new Set([
|
|
25891
|
-
"hook_event_name",
|
|
25892
|
-
"session_id",
|
|
25893
|
-
"cwd",
|
|
25894
|
-
"model",
|
|
25895
|
-
"permission_mode",
|
|
25896
|
-
"transcript_path",
|
|
25897
|
-
"turn_id",
|
|
25898
|
-
"agent_id",
|
|
25899
|
-
"agent_type"
|
|
25900
|
-
]),
|
|
25901
|
-
SubagentStop: new Set([
|
|
25902
|
-
"hook_event_name",
|
|
25903
|
-
"session_id",
|
|
25904
|
-
"cwd",
|
|
25905
|
-
"model",
|
|
25906
|
-
"permission_mode",
|
|
25907
|
-
"transcript_path",
|
|
25908
|
-
"turn_id",
|
|
25909
|
-
"agent_id",
|
|
25910
|
-
"agent_type",
|
|
25911
|
-
"agent_transcript_path",
|
|
25912
|
-
"last_assistant_message",
|
|
25913
|
-
"stop_hook_active"
|
|
25914
|
-
])
|
|
26428
|
+
]
|
|
25915
26429
|
};
|
|
25916
|
-
var
|
|
26430
|
+
var codexDescriptor = (host) => ({ ...CODEX_DESCRIPTOR, host });
|
|
26431
|
+
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
26432
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
26433
|
+
|
|
26434
|
+
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
26435
|
+
var BLOCKING = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
26436
|
+
|
|
26437
|
+
// packages/workit-core/src/hooks/run.ts
|
|
26438
|
+
if (false) {}
|
|
26439
|
+
// packages/workit-codex/hooks/workit-hook.ts
|
|
26440
|
+
var codexCapabilities = (host, availability = {}) => capabilitiesFor(codexDescriptor(host), {
|
|
26441
|
+
"session.start": availability.sessionStart,
|
|
26442
|
+
"shell.pre": availability.preToolUse,
|
|
26443
|
+
"subagent.start": availability.subagentStart,
|
|
26444
|
+
"subagent.stop": availability.subagentStop
|
|
26445
|
+
});
|
|
25917
26446
|
if (false)
|
|
25918
26447
|
;
|
|
25919
26448
|
|