@brainervirus/workit-codex 2.1.5 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +1410 -962
- package/dist/workit-hook.js +3903 -2885
- 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
|
|
@@ -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,21 +17517,19 @@ 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,
|
|
@@ -17154,55 +17561,118 @@ class TaskStore {
|
|
|
17154
17561
|
return path4.join(this.workitDir, ".gitignore");
|
|
17155
17562
|
}
|
|
17156
17563
|
}
|
|
17157
|
-
// packages/workit-core/src/core/
|
|
17158
|
-
|
|
17159
|
-
|
|
17160
|
-
|
|
17161
|
-
|
|
17162
|
-
|
|
17163
|
-
|
|
17164
|
-
|
|
17165
|
-
|
|
17166
|
-
|
|
17167
|
-
|
|
17168
|
-
|
|
17169
|
-
|
|
17170
|
-
|
|
17171
|
-
|
|
17172
|
-
|
|
17173
|
-
|
|
17174
|
-
|
|
17175
|
-
|
|
17176
|
-
|
|
17177
|
-
|
|
17178
|
-
|
|
17179
|
-
|
|
17180
|
-
|
|
17181
|
-
|
|
17182
|
-
|
|
17183
|
-
|
|
17184
|
-
|
|
17185
|
-
|
|
17186
|
-
|
|
17187
|
-
|
|
17188
|
-
|
|
17189
|
-
|
|
17190
|
-
|
|
17191
|
-
|
|
17192
|
-
|
|
17193
|
-
|
|
17194
|
-
|
|
17195
|
-
|
|
17196
|
-
|
|
17197
|
-
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
|
|
17201
|
-
|
|
17202
|
-
|
|
17203
|
-
|
|
17204
|
-
|
|
17205
|
-
|
|
17564
|
+
// packages/workit-core/src/core/workers.ts
|
|
17565
|
+
var same = (left, right) => {
|
|
17566
|
+
try {
|
|
17567
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
17568
|
+
} catch {
|
|
17569
|
+
return false;
|
|
17570
|
+
}
|
|
17571
|
+
};
|
|
17572
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
17573
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
17574
|
+
var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
|
|
17575
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
17576
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
|
|
17577
|
+
const code = char.charCodeAt(0);
|
|
17578
|
+
return code < 32 || code === 127;
|
|
17579
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
17580
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
17581
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
17582
|
+
var currentWriterOwnsTask = (task, workspace, caller) => {
|
|
17583
|
+
const owner = workspace.writer?.owner;
|
|
17584
|
+
return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
|
|
17585
|
+
};
|
|
17586
|
+
function assertProductWriteAllowed(input) {
|
|
17587
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
17588
|
+
return failure2("invalid_input", "invalid product write path");
|
|
17589
|
+
if (!input.store)
|
|
17590
|
+
return failure2("permission_denied", "write authorization requires core state");
|
|
17591
|
+
const task = input.store.readTask(input.task.id);
|
|
17592
|
+
if (!task.ok)
|
|
17593
|
+
return task;
|
|
17594
|
+
const workspace = input.store.readWorkspace();
|
|
17595
|
+
if (!workspace.ok)
|
|
17596
|
+
return workspace;
|
|
17597
|
+
if (!workspace.data)
|
|
17598
|
+
return failure2("not_found", "workspace not found");
|
|
17599
|
+
const workspaceRecord = workspace.data;
|
|
17600
|
+
if (task.data.status !== "active")
|
|
17601
|
+
return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
17602
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
17603
|
+
return failure2("recovery_required", "task and workspace bindings are invalid");
|
|
17604
|
+
const writer = workspaceRecord.writer;
|
|
17605
|
+
if (!writer)
|
|
17606
|
+
return failure2("permission_denied", "checkout has no writer owner");
|
|
17607
|
+
if (writer.state === "uncertain")
|
|
17608
|
+
return failure2("recovery_required", "checkout writer requires recovery", {
|
|
17609
|
+
owner: writer.owner
|
|
17610
|
+
});
|
|
17611
|
+
const caller = callerValue(input.caller);
|
|
17612
|
+
const workerId = workerIdOf(input.caller);
|
|
17613
|
+
const owner = writer.owner;
|
|
17614
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
17615
|
+
return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
17616
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
17617
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
17618
|
+
return failure2("permission_denied", "only implementers can own product writes");
|
|
17619
|
+
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))))
|
|
17620
|
+
return failure2("recovery_required", "worker session is not an active writer");
|
|
17621
|
+
return success(null, workspaceRecord.revision, owner);
|
|
17622
|
+
}
|
|
17623
|
+
|
|
17624
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
17625
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
17626
|
+
import * as fs8 from "node:fs";
|
|
17627
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
17628
|
+
import path10 from "node:path";
|
|
17629
|
+
|
|
17630
|
+
// packages/workit-core/src/core/authority.ts
|
|
17631
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
17632
|
+
import * as fs7 from "node:fs";
|
|
17633
|
+
import path9 from "node:path";
|
|
17634
|
+
|
|
17635
|
+
// packages/workit-core/src/core/auto-approval.ts
|
|
17636
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
17637
|
+
import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
|
|
17638
|
+
import path8 from "node:path";
|
|
17639
|
+
|
|
17640
|
+
// packages/workit-core/src/core/workspaces.ts
|
|
17641
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
17642
|
+
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
17643
|
+
import path6 from "node:path";
|
|
17644
|
+
|
|
17645
|
+
// packages/workit-core/src/core/config.ts
|
|
17646
|
+
import {
|
|
17647
|
+
copyFileSync,
|
|
17648
|
+
cpSync,
|
|
17649
|
+
existsSync as existsSync3,
|
|
17650
|
+
mkdirSync as mkdirSync2,
|
|
17651
|
+
readFileSync as readFileSync2,
|
|
17652
|
+
readdirSync as readdirSync2,
|
|
17653
|
+
writeFileSync as writeFileSync2
|
|
17654
|
+
} from "node:fs";
|
|
17655
|
+
import os from "node:os";
|
|
17656
|
+
import path5 from "node:path";
|
|
17657
|
+
|
|
17658
|
+
// packages/workit-core/src/core/boundary.ts
|
|
17659
|
+
import { statSync as statSync2 } from "node:fs";
|
|
17660
|
+
var EVENT = {
|
|
17661
|
+
initialization: "initialization",
|
|
17662
|
+
provenance: "provenance",
|
|
17663
|
+
assets: "assets",
|
|
17664
|
+
configurationSource: "configuration_source",
|
|
17665
|
+
hooks: "hooks",
|
|
17666
|
+
mcpConnection: "mcp_connection",
|
|
17667
|
+
migration: "migration",
|
|
17668
|
+
installSteps: "install_steps",
|
|
17669
|
+
uncaughtFailure: "uncaught_failure",
|
|
17670
|
+
toolsFailed: "tools_failed",
|
|
17671
|
+
doctor: "doctor"
|
|
17672
|
+
};
|
|
17673
|
+
var errorDetail = (err) => {
|
|
17674
|
+
if (err instanceof Error) {
|
|
17675
|
+
return { error_name: err.name, error: err.message };
|
|
17206
17676
|
}
|
|
17207
17677
|
return { error_name: "unknown", error: String(err) };
|
|
17208
17678
|
};
|
|
@@ -17829,13 +18299,13 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
17829
18299
|
};
|
|
17830
18300
|
|
|
17831
18301
|
// packages/workit-core/src/core/vcs-config.ts
|
|
17832
|
-
import
|
|
18302
|
+
import fs6 from "node:fs";
|
|
17833
18303
|
import path7 from "node:path";
|
|
17834
|
-
import { spawnSync } from "node:child_process";
|
|
18304
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
17835
18305
|
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
|
|
17836
18306
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
17837
18307
|
var remoteProvider = (cwd) => {
|
|
17838
|
-
const r =
|
|
18308
|
+
const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
17839
18309
|
if (r.status !== 0)
|
|
17840
18310
|
return null;
|
|
17841
18311
|
const url = (r.stdout ?? "").trim();
|
|
@@ -17872,7 +18342,7 @@ var remoteEndpoint = (remote) => {
|
|
|
17872
18342
|
var configuredSshEndpoint = (host) => {
|
|
17873
18343
|
if (host.startsWith("-"))
|
|
17874
18344
|
return null;
|
|
17875
|
-
const resolved =
|
|
18345
|
+
const resolved = spawnSync2("ssh", ["-G", host], {
|
|
17876
18346
|
encoding: "utf8",
|
|
17877
18347
|
timeout: 5000,
|
|
17878
18348
|
maxBuffer: 1024 * 1024,
|
|
@@ -17922,7 +18392,7 @@ var readVcsConfig = () => {
|
|
|
17922
18392
|
const file = vcsConfigPath();
|
|
17923
18393
|
let raw;
|
|
17924
18394
|
try {
|
|
17925
|
-
raw =
|
|
18395
|
+
raw = fs6.readFileSync(file, "utf8");
|
|
17926
18396
|
} catch {
|
|
17927
18397
|
return { status: "missing", path: file, config: {} };
|
|
17928
18398
|
}
|
|
@@ -18023,7 +18493,7 @@ function vcsCliIdentity(cwd) {
|
|
|
18023
18493
|
PATH: process.env.PATH ?? "",
|
|
18024
18494
|
...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
|
|
18025
18495
|
};
|
|
18026
|
-
const result =
|
|
18496
|
+
const result = spawnSync2(bin, ["api", "user"], {
|
|
18027
18497
|
cwd: root,
|
|
18028
18498
|
encoding: "utf8",
|
|
18029
18499
|
env
|
|
@@ -18065,7 +18535,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18065
18535
|
`).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
|
|
18066
18536
|
descriptionPreview: desc.slice(0, 600)
|
|
18067
18537
|
});
|
|
18068
|
-
const remoteResult =
|
|
18538
|
+
const remoteResult = spawnSync2("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
18069
18539
|
cwd,
|
|
18070
18540
|
encoding: "utf8"
|
|
18071
18541
|
});
|
|
@@ -18081,7 +18551,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18081
18551
|
if (!hostingApiHostMatches(remote, host))
|
|
18082
18552
|
return { ok: false, error: "PR style target does not match the provider host" };
|
|
18083
18553
|
if (provider === "gitlab") {
|
|
18084
|
-
const run = (args) =>
|
|
18554
|
+
const run = (args) => spawnSync2("glab", ["api", ...args], {
|
|
18085
18555
|
cwd,
|
|
18086
18556
|
encoding: "utf8",
|
|
18087
18557
|
env
|
|
@@ -18101,7 +18571,7 @@ function mergedPrStyle(limit = 6, cwd) {
|
|
|
18101
18571
|
});
|
|
18102
18572
|
}
|
|
18103
18573
|
} else if (provider === "github") {
|
|
18104
|
-
const r =
|
|
18574
|
+
const r = spawnSync2("gh", [
|
|
18105
18575
|
"pr",
|
|
18106
18576
|
"list",
|
|
18107
18577
|
"--repo",
|
|
@@ -18160,66 +18630,6 @@ var verifyPushIdentity = (root, provider, account) => {
|
|
|
18160
18630
|
return { ok: true };
|
|
18161
18631
|
};
|
|
18162
18632
|
|
|
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
18633
|
// packages/workit-core/src/core/auto-approval.ts
|
|
18224
18634
|
var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
|
|
18225
18635
|
var OPERATION_CLASS = {
|
|
@@ -18506,7 +18916,7 @@ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
|
|
|
18506
18916
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
18507
18917
|
let root;
|
|
18508
18918
|
try {
|
|
18509
|
-
root =
|
|
18919
|
+
root = fs7.realpathSync(checkoutRoot);
|
|
18510
18920
|
} catch {
|
|
18511
18921
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
18512
18922
|
}
|
|
@@ -18529,8 +18939,8 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
18529
18939
|
if (!segment)
|
|
18530
18940
|
continue;
|
|
18531
18941
|
current = path9.join(current, segment);
|
|
18532
|
-
const stat =
|
|
18533
|
-
const real =
|
|
18942
|
+
const stat = fs7.lstatSync(current);
|
|
18943
|
+
const real = fs7.realpathSync(current);
|
|
18534
18944
|
if (real !== root && !real.startsWith(`${root}${path9.sep}`))
|
|
18535
18945
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
18536
18946
|
fields: [
|
|
@@ -18547,7 +18957,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
18547
18957
|
if (current !== target && !stat.isDirectory())
|
|
18548
18958
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
18549
18959
|
}
|
|
18550
|
-
if (digestBytes2(
|
|
18960
|
+
if (digestBytes2(fs7.readFileSync(target)) !== reference.digest)
|
|
18551
18961
|
return failure2("permission_denied", "approved document bytes have changed");
|
|
18552
18962
|
} catch {
|
|
18553
18963
|
return failure2("invalid_input", "approved document is unavailable");
|
|
@@ -18910,18 +19320,41 @@ function reconcileAction(input) {
|
|
|
18910
19320
|
var bindingCovers = scopeCovers;
|
|
18911
19321
|
var verifyDecisionContent = verifyContentRefs;
|
|
18912
19322
|
|
|
18913
|
-
// packages/workit-core/src/core/
|
|
18914
|
-
var
|
|
18915
|
-
|
|
18916
|
-
|
|
18917
|
-
|
|
18918
|
-
|
|
18919
|
-
|
|
18920
|
-
|
|
18921
|
-
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
19323
|
+
// packages/workit-core/src/core/task-evaluation.ts
|
|
19324
|
+
var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
|
|
19325
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
|
|
19326
|
+
var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
|
|
19327
|
+
var canonicalPath = (value) => {
|
|
19328
|
+
if (typeof value !== "string" || !value)
|
|
19329
|
+
return null;
|
|
19330
|
+
if (value.includes("\\") || value.split("/").includes(".."))
|
|
19331
|
+
return null;
|
|
19332
|
+
const normalized = path10.posix.normalize(value);
|
|
19333
|
+
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
19334
|
+
return null;
|
|
19335
|
+
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
19336
|
+
};
|
|
19337
|
+
var canonicalScope = (scope) => {
|
|
19338
|
+
const paths = scope.paths.length ? scope.paths : ["."];
|
|
19339
|
+
const normalizedPaths = paths.map(canonicalPath);
|
|
19340
|
+
const normalizedExclusions = scope.exclusions.map(canonicalPath);
|
|
19341
|
+
if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
|
|
19342
|
+
return null;
|
|
19343
|
+
return {
|
|
19344
|
+
description: scope.description,
|
|
19345
|
+
paths: [...new Set(normalizedPaths)],
|
|
19346
|
+
exclusions: [...new Set(normalizedExclusions)]
|
|
19347
|
+
};
|
|
19348
|
+
};
|
|
19349
|
+
var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
|
|
19350
|
+
var scopeMatches = (value, scope) => {
|
|
19351
|
+
const normalized = canonicalScope(scope);
|
|
19352
|
+
if (!normalized)
|
|
19353
|
+
return false;
|
|
19354
|
+
const paths = normalized.paths;
|
|
19355
|
+
return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
|
|
19356
|
+
};
|
|
19357
|
+
var pathRelevant = (value, scope) => scopeMatches(value, scope);
|
|
18925
19358
|
var compareCodeUnits2 = (left, right) => {
|
|
18926
19359
|
const length = Math.min(left.length, right.length);
|
|
18927
19360
|
for (let index = 0;index < length; index += 1) {
|
|
@@ -18931,362 +19364,8 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
18931
19364
|
}
|
|
18932
19365
|
return left.length - right.length;
|
|
18933
19366
|
};
|
|
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
19367
|
var gitPaths = (root) => {
|
|
19289
|
-
const result =
|
|
19368
|
+
const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
19290
19369
|
cwd: root,
|
|
19291
19370
|
encoding: "buffer"
|
|
19292
19371
|
});
|
|
@@ -19299,7 +19378,7 @@ var gitPaths = (root) => {
|
|
|
19299
19378
|
};
|
|
19300
19379
|
}
|
|
19301
19380
|
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
|
|
19302
|
-
const stagedDeleted =
|
|
19381
|
+
const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
19303
19382
|
if (stagedDeleted.status !== 0) {
|
|
19304
19383
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
19305
19384
|
if (!/not a git repository/i.test(stderr))
|
|
@@ -19312,7 +19391,7 @@ var gitPaths = (root) => {
|
|
|
19312
19391
|
var walk = (root, directory, output) => {
|
|
19313
19392
|
let names;
|
|
19314
19393
|
try {
|
|
19315
|
-
names =
|
|
19394
|
+
names = fs8.readdirSync(directory);
|
|
19316
19395
|
} catch {
|
|
19317
19396
|
return true;
|
|
19318
19397
|
}
|
|
@@ -19323,7 +19402,7 @@ var walk = (root, directory, output) => {
|
|
|
19323
19402
|
const target = path10.join(directory, name);
|
|
19324
19403
|
let stat;
|
|
19325
19404
|
try {
|
|
19326
|
-
stat =
|
|
19405
|
+
stat = fs8.lstatSync(target);
|
|
19327
19406
|
} catch {
|
|
19328
19407
|
uncertain = true;
|
|
19329
19408
|
continue;
|
|
@@ -19342,10 +19421,10 @@ var scopeRoots = (root, scope) => {
|
|
|
19342
19421
|
if (!inside(root, target))
|
|
19343
19422
|
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
19423
|
let ancestor = target;
|
|
19345
|
-
while (!
|
|
19424
|
+
while (!fs8.existsSync(ancestor) && ancestor !== root)
|
|
19346
19425
|
ancestor = path10.dirname(ancestor);
|
|
19347
19426
|
try {
|
|
19348
|
-
if (!inside(root,
|
|
19427
|
+
if (!inside(root, fs8.realpathSync(ancestor)))
|
|
19349
19428
|
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
19429
|
} catch {
|
|
19351
19430
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -19357,7 +19436,7 @@ var scopeRoots = (root, scope) => {
|
|
|
19357
19436
|
function captureCandidate(root, scope, environment = []) {
|
|
19358
19437
|
let checkout;
|
|
19359
19438
|
try {
|
|
19360
|
-
checkout =
|
|
19439
|
+
checkout = fs8.realpathSync(root);
|
|
19361
19440
|
} catch {
|
|
19362
19441
|
return failure2("invalid_input", "candidate root does not exist");
|
|
19363
19442
|
}
|
|
@@ -19375,11 +19454,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19375
19454
|
if (item === ".git" || item === ".workit")
|
|
19376
19455
|
continue;
|
|
19377
19456
|
names.add(item);
|
|
19378
|
-
if (!
|
|
19457
|
+
if (!fs8.existsSync(target))
|
|
19379
19458
|
continue;
|
|
19380
19459
|
let stat;
|
|
19381
19460
|
try {
|
|
19382
|
-
stat =
|
|
19461
|
+
stat = fs8.lstatSync(target);
|
|
19383
19462
|
} catch {
|
|
19384
19463
|
uncertain = true;
|
|
19385
19464
|
continue;
|
|
@@ -19397,15 +19476,15 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19397
19476
|
continue;
|
|
19398
19477
|
const target = path10.join(checkout, item);
|
|
19399
19478
|
try {
|
|
19400
|
-
const stat =
|
|
19479
|
+
const stat = fs8.lstatSync(target);
|
|
19401
19480
|
if (stat.isSymbolicLink()) {
|
|
19402
|
-
const link =
|
|
19481
|
+
const link = fs8.readlinkSync(target);
|
|
19403
19482
|
files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
|
|
19404
19483
|
} else if (stat.isFile()) {
|
|
19405
19484
|
files.push({
|
|
19406
19485
|
path: item,
|
|
19407
19486
|
kind: "file",
|
|
19408
|
-
digest: digestBytes3(
|
|
19487
|
+
digest: digestBytes3(fs8.readFileSync(target)),
|
|
19409
19488
|
executable: (stat.mode & 73) !== 0
|
|
19410
19489
|
});
|
|
19411
19490
|
} else if (!stat.isDirectory()) {
|
|
@@ -19427,10 +19506,10 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
19427
19506
|
const namesByEnvironment = supplied.map(({ name }) => name);
|
|
19428
19507
|
if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
|
|
19429
19508
|
return failure2("invalid_input", "candidate environment names must be unique and non-empty");
|
|
19430
|
-
const values = supplied.toSorted((left, right) =>
|
|
19509
|
+
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
19431
19510
|
if (values.some(({ value }) => value === null))
|
|
19432
19511
|
completeness = "uncertain";
|
|
19433
|
-
const headResult =
|
|
19512
|
+
const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
19434
19513
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
19435
19514
|
completeness = "uncertain";
|
|
19436
19515
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -19761,140 +19840,582 @@ var assuranceFor = (id, definition, capabilities) => {
|
|
|
19761
19840
|
assurance: "unavailable",
|
|
19762
19841
|
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
19763
19842
|
};
|
|
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) };
|
|
19843
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
19844
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
19845
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
19846
|
+
};
|
|
19847
|
+
function selectMethods(policy, capabilities) {
|
|
19848
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
19849
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
|
|
19850
|
+
if (requirements.length === 0)
|
|
19851
|
+
return [];
|
|
19852
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
19853
|
+
const reasons = [
|
|
19854
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
19855
|
+
...capability.reasons
|
|
19856
|
+
];
|
|
19857
|
+
if (capability.assurance === "unavailable")
|
|
19858
|
+
reasons.unshift("required capability is unavailable");
|
|
19859
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
19860
|
+
});
|
|
19861
|
+
}
|
|
19862
|
+
|
|
19863
|
+
// packages/workit-core/src/core/task-context.ts
|
|
19864
|
+
function reconcileResume(view, observations = []) {
|
|
19865
|
+
if (observations.length > 0)
|
|
19866
|
+
return failure2("permission_denied", "worker observations require native host verification");
|
|
19867
|
+
const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
|
|
19868
|
+
if (!candidate.ok)
|
|
19869
|
+
return candidate;
|
|
19870
|
+
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
19871
|
+
const blockers = [...view.task.progress.blockers];
|
|
19872
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
19873
|
+
blockers.push({
|
|
19874
|
+
reason: "worker state requires reconciliation",
|
|
19875
|
+
dependentAction: "resume",
|
|
19876
|
+
refs: []
|
|
19877
|
+
});
|
|
19878
|
+
return success(null, null, {
|
|
19879
|
+
candidate: candidate.data,
|
|
19880
|
+
staleEvidenceIds,
|
|
19881
|
+
workerUpdates: [],
|
|
19882
|
+
blockers,
|
|
19883
|
+
reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
|
|
19884
|
+
});
|
|
19885
|
+
}
|
|
19886
|
+
var COMPACT_MAX_BYTES = 4096;
|
|
19887
|
+
var COMPACT_MAX_ITEMS = 8;
|
|
19888
|
+
var COMPACT_TEXT_BYTES = 128;
|
|
19889
|
+
var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
19890
|
+
var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
19891
|
+
var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
19892
|
+
var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
|
|
19893
|
+
const separator = match.search(/[:=]/u);
|
|
19894
|
+
if (separator >= 0)
|
|
19895
|
+
return `${match.slice(0, separator)}=<redacted>`;
|
|
19896
|
+
const space = match.search(/\s/u);
|
|
19897
|
+
return `${match.slice(0, space)} <redacted>`;
|
|
19898
|
+
}).slice(0, limit);
|
|
19899
|
+
var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
|
|
19900
|
+
var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
|
|
19901
|
+
var compactDecisionChoice = (entry) => {
|
|
19902
|
+
const stated = entry.data.binding.statedChoice?.text;
|
|
19903
|
+
const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
|
|
19904
|
+
const source = stated?.trim() || approved.trim() || null;
|
|
19905
|
+
return compactText(source, COMPACT_TEXT_BYTES);
|
|
19906
|
+
};
|
|
19907
|
+
var compactReference = (ref) => {
|
|
19908
|
+
if (ref.kind === "file")
|
|
19909
|
+
return {
|
|
19910
|
+
kind: "file",
|
|
19911
|
+
path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
|
|
19912
|
+
};
|
|
19913
|
+
if (ref.kind === "record")
|
|
19914
|
+
return {
|
|
19915
|
+
kind: "record",
|
|
19916
|
+
collection: ref.collection,
|
|
19917
|
+
id: ref.id
|
|
19918
|
+
};
|
|
19919
|
+
return { kind: ref.kind };
|
|
19767
19920
|
};
|
|
19768
|
-
function
|
|
19769
|
-
|
|
19770
|
-
const
|
|
19771
|
-
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
|
|
19778
|
-
|
|
19779
|
-
|
|
19780
|
-
|
|
19781
|
-
|
|
19921
|
+
function compactTaskContext(view) {
|
|
19922
|
+
const decisions = view.task.decisions.slice().toSorted((left, right) => {
|
|
19923
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
19924
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
19925
|
+
if (leftAt !== rightAt)
|
|
19926
|
+
return leftAt > rightAt ? -1 : 1;
|
|
19927
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
19928
|
+
}).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
|
|
19929
|
+
id: entry.id,
|
|
19930
|
+
purpose: entry.data.purpose,
|
|
19931
|
+
status: entry.data.response,
|
|
19932
|
+
digest: entry.data.digest,
|
|
19933
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
|
|
19934
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
19935
|
+
}));
|
|
19936
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
19937
|
+
id: method.id,
|
|
19938
|
+
assurance: method.assurance,
|
|
19939
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
19940
|
+
})) : [];
|
|
19941
|
+
const gaps = Array.from(new Set([
|
|
19942
|
+
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
19943
|
+
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
19944
|
+
...view.task.progress.blockers.map((entry) => entry.reason)
|
|
19945
|
+
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
19946
|
+
const context = {
|
|
19947
|
+
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
19948
|
+
status: view.task.status,
|
|
19949
|
+
decisions,
|
|
19950
|
+
methods,
|
|
19951
|
+
gaps,
|
|
19952
|
+
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
19953
|
+
};
|
|
19954
|
+
let encoded = JSON.stringify(context);
|
|
19955
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
19956
|
+
if (context.gaps.length)
|
|
19957
|
+
context.gaps.pop();
|
|
19958
|
+
else if (context.decisions.length)
|
|
19959
|
+
context.decisions.pop();
|
|
19960
|
+
else
|
|
19961
|
+
context.methods.pop();
|
|
19962
|
+
encoded = JSON.stringify(context);
|
|
19963
|
+
}
|
|
19964
|
+
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
19965
|
+
context.objective = compactText(context.objective, 64) ?? "";
|
|
19966
|
+
context.nextAction = compactText(context.nextAction, 64);
|
|
19967
|
+
encoded = JSON.stringify(context);
|
|
19968
|
+
}
|
|
19969
|
+
return encoded;
|
|
19782
19970
|
}
|
|
19971
|
+
var exportDigest = (bundle) => sha256({
|
|
19972
|
+
schemaVersion: bundle.schemaVersion,
|
|
19973
|
+
exportedAt: bundle.exportedAt,
|
|
19974
|
+
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
19975
|
+
task: bundle.task
|
|
19976
|
+
});
|
|
19977
|
+
// packages/workit-core/src/core/external-action.ts
|
|
19978
|
+
var chainStepSchema = union([
|
|
19979
|
+
string2().min(1),
|
|
19980
|
+
object({ branch: string2().min(1) }).strict(),
|
|
19981
|
+
object({ pr: literal(true) }).strict()
|
|
19982
|
+
]);
|
|
19983
|
+
var externalActionSchema = discriminatedUnion("operation", [
|
|
19984
|
+
object({
|
|
19985
|
+
operation: literal("git.branch_setup"),
|
|
19986
|
+
payload: object({
|
|
19987
|
+
action: _enum(["setup", "reapply_stash"]).optional(),
|
|
19988
|
+
sdd_dir: string2().optional(),
|
|
19989
|
+
target_branch: string2().optional(),
|
|
19990
|
+
stash: _enum(["yes", "no"]).optional(),
|
|
19991
|
+
cwd: string2().min(1).optional()
|
|
19992
|
+
}).strict()
|
|
19993
|
+
}).strict(),
|
|
19994
|
+
object({
|
|
19995
|
+
operation: literal("git.commit"),
|
|
19996
|
+
payload: object({
|
|
19997
|
+
message: string2().min(1).optional(),
|
|
19998
|
+
plan_steps: array(chainStepSchema).min(1).max(32).optional(),
|
|
19999
|
+
plan_branch: string2().min(1).optional(),
|
|
20000
|
+
cwd: string2().min(1).optional()
|
|
20001
|
+
}).strict()
|
|
20002
|
+
}).strict(),
|
|
20003
|
+
object({
|
|
20004
|
+
operation: literal("git.push"),
|
|
20005
|
+
payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
|
|
20006
|
+
}).strict(),
|
|
20007
|
+
object({
|
|
20008
|
+
operation: literal("hosting.pull_request"),
|
|
20009
|
+
payload: object({
|
|
20010
|
+
title: string2().min(1),
|
|
20011
|
+
body: string2().optional(),
|
|
20012
|
+
draft: boolean2().optional(),
|
|
20013
|
+
target_branch: string2().optional(),
|
|
20014
|
+
babysit: boolean2().optional(),
|
|
20015
|
+
cwd: string2().min(1).optional()
|
|
20016
|
+
}).strict()
|
|
20017
|
+
}).strict(),
|
|
20018
|
+
object({
|
|
20019
|
+
operation: literal("hosting.merge"),
|
|
20020
|
+
payload: object({
|
|
20021
|
+
target_branch: string2().min(1).optional(),
|
|
20022
|
+
source_branch: string2().min(1).optional(),
|
|
20023
|
+
cwd: string2().min(1).optional()
|
|
20024
|
+
}).strict()
|
|
20025
|
+
}).strict(),
|
|
20026
|
+
object({
|
|
20027
|
+
operation: literal("hosting.delete_branch"),
|
|
20028
|
+
payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
|
|
20029
|
+
}).strict(),
|
|
20030
|
+
object({
|
|
20031
|
+
operation: literal("youtrack.update"),
|
|
20032
|
+
payload: object({
|
|
20033
|
+
issueId: string2().min(1),
|
|
20034
|
+
markdown: string2().min(1),
|
|
20035
|
+
minutes: number2().positive().optional()
|
|
20036
|
+
}).strict()
|
|
20037
|
+
}).strict(),
|
|
20038
|
+
object({
|
|
20039
|
+
operation: literal("youtrack.time"),
|
|
20040
|
+
payload: object({
|
|
20041
|
+
issueId: string2().min(1),
|
|
20042
|
+
minutes: number2().positive(),
|
|
20043
|
+
text: string2().optional(),
|
|
20044
|
+
dateMs: number2().finite().optional()
|
|
20045
|
+
}).strict()
|
|
20046
|
+
}).strict(),
|
|
20047
|
+
object({
|
|
20048
|
+
operation: literal("youtrack.meeting"),
|
|
20049
|
+
payload: object({
|
|
20050
|
+
issueId: string2().min(1),
|
|
20051
|
+
minutes: number2().positive(),
|
|
20052
|
+
text: string2().min(1)
|
|
20053
|
+
}).strict()
|
|
20054
|
+
}).strict(),
|
|
20055
|
+
object({
|
|
20056
|
+
operation: literal("changelog.apply"),
|
|
20057
|
+
payload: object({
|
|
20058
|
+
entries: union([
|
|
20059
|
+
record(string2(), array(string2())),
|
|
20060
|
+
array(object({ category: string2().min(1), text: string2().min(1) }).strict())
|
|
20061
|
+
]).optional(),
|
|
20062
|
+
path: string2().optional(),
|
|
20063
|
+
normalize_only: boolean2().optional()
|
|
20064
|
+
}).strict()
|
|
20065
|
+
}).strict(),
|
|
20066
|
+
object({
|
|
20067
|
+
operation: literal("context.read"),
|
|
20068
|
+
payload: object({
|
|
20069
|
+
kind: _enum([
|
|
20070
|
+
"git",
|
|
20071
|
+
"pr",
|
|
20072
|
+
"youtrack",
|
|
20073
|
+
"github_issue",
|
|
20074
|
+
"gitlab_issue",
|
|
20075
|
+
"changelog",
|
|
20076
|
+
"release",
|
|
20077
|
+
"affected"
|
|
20078
|
+
]),
|
|
20079
|
+
range: string2().optional(),
|
|
20080
|
+
issueId: string2().optional(),
|
|
20081
|
+
issueUrl: string2().optional(),
|
|
20082
|
+
issueRef: string2().optional(),
|
|
20083
|
+
mode: string2().optional(),
|
|
20084
|
+
specPath: string2().optional(),
|
|
20085
|
+
planPath: string2().optional(),
|
|
20086
|
+
cwd: string2().min(1).optional()
|
|
20087
|
+
}).strict()
|
|
20088
|
+
}).strict()
|
|
20089
|
+
]);
|
|
20090
|
+
// packages/workit-core/src/core/task-engine.ts
|
|
20091
|
+
import { realpathSync as realpathSync6 } from "node:fs";
|
|
19783
20092
|
|
|
19784
|
-
// packages/workit-core/src/core/
|
|
19785
|
-
|
|
19786
|
-
|
|
19787
|
-
|
|
19788
|
-
|
|
19789
|
-
|
|
19790
|
-
|
|
19791
|
-
|
|
19792
|
-
|
|
19793
|
-
|
|
19794
|
-
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
20093
|
+
// packages/workit-core/src/core/policy-resolver.ts
|
|
20094
|
+
var resolverInputSchema = object({
|
|
20095
|
+
intent: intentSchema,
|
|
20096
|
+
assessment: assessmentSchema,
|
|
20097
|
+
constraints: array(constraintSchema),
|
|
20098
|
+
prior: object({
|
|
20099
|
+
decisions: array(decisionSchema),
|
|
20100
|
+
findings: array(findingSchema),
|
|
20101
|
+
requirements: array(requirementSchema)
|
|
20102
|
+
}).strict(),
|
|
20103
|
+
preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
|
|
20104
|
+
}).strict();
|
|
20105
|
+
var compareCodeUnits3 = (left, right) => {
|
|
20106
|
+
const length = Math.min(left.length, right.length);
|
|
20107
|
+
for (let index = 0;index < length; index += 1) {
|
|
20108
|
+
const difference = left.charCodeAt(index) - right.charCodeAt(index);
|
|
20109
|
+
if (difference !== 0)
|
|
20110
|
+
return difference;
|
|
20111
|
+
}
|
|
20112
|
+
return left.length - right.length;
|
|
20113
|
+
};
|
|
20114
|
+
var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
|
|
20115
|
+
var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
|
|
20116
|
+
var normalizedScope = (scope) => ({
|
|
20117
|
+
description: scope.description,
|
|
20118
|
+
paths: stableUnique(scope.paths),
|
|
20119
|
+
exclusions: stableUnique(scope.exclusions)
|
|
20120
|
+
});
|
|
20121
|
+
var normalizedRefList = (values) => stableList(values);
|
|
20122
|
+
var normalizeAssessment = (assessment) => ({
|
|
20123
|
+
facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
|
|
20124
|
+
signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
|
|
20125
|
+
name,
|
|
20126
|
+
{ ...signal, refs: normalizedRefList(signal.refs) }
|
|
20127
|
+
])),
|
|
20128
|
+
consequences: stableList(assessment.consequences.map((consequence) => ({
|
|
20129
|
+
...consequence,
|
|
20130
|
+
fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
|
|
20131
|
+
}))),
|
|
20132
|
+
verification: stableList(assessment.verification.map((verification) => ({
|
|
20133
|
+
...verification,
|
|
20134
|
+
scope: normalizedScope(verification.scope),
|
|
20135
|
+
availableChecks: normalizedRefList(verification.availableChecks),
|
|
20136
|
+
gaps: stableUnique(verification.gaps)
|
|
20137
|
+
})))
|
|
20138
|
+
});
|
|
20139
|
+
var normalizeConstraint = (constraint) => ({
|
|
20140
|
+
...constraint,
|
|
20141
|
+
source: constraint.source,
|
|
20142
|
+
requires: stableList(constraint.requires.map((requirement) => ({
|
|
20143
|
+
...requirement,
|
|
20144
|
+
scope: normalizedScope(requirement.scope),
|
|
20145
|
+
refs: normalizedRefList(requirement.refs)
|
|
20146
|
+
})))
|
|
20147
|
+
});
|
|
20148
|
+
var normalizeDecision = (decision) => ({
|
|
20149
|
+
...decision,
|
|
20150
|
+
binding: {
|
|
20151
|
+
...decision.binding,
|
|
20152
|
+
scope: normalizedScope(decision.binding.scope),
|
|
20153
|
+
contentRefs: normalizedRefList(decision.binding.contentRefs)
|
|
20154
|
+
},
|
|
20155
|
+
requirementIds: stableUnique(decision.requirementIds)
|
|
20156
|
+
});
|
|
20157
|
+
var normalizeIntent = (intent) => ({
|
|
20158
|
+
...intent,
|
|
20159
|
+
scope: normalizedScope(intent.scope),
|
|
20160
|
+
authorityRefs: normalizedRefList(intent.authorityRefs)
|
|
20161
|
+
});
|
|
20162
|
+
var normalizePrior = (prior) => ({
|
|
20163
|
+
decisions: stableList(prior.decisions.map(normalizeDecision)),
|
|
20164
|
+
findings: stableList(prior.findings.map((finding) => ({
|
|
20165
|
+
...finding,
|
|
20166
|
+
scope: normalizedScope(finding.scope),
|
|
20167
|
+
refs: normalizedRefList(finding.refs),
|
|
20168
|
+
resolution: finding.resolution ? {
|
|
20169
|
+
...finding.resolution,
|
|
20170
|
+
evidenceIds: stableUnique(finding.resolution.evidenceIds),
|
|
20171
|
+
decisionIds: stableUnique(finding.resolution.decisionIds)
|
|
20172
|
+
} : null
|
|
20173
|
+
}))),
|
|
20174
|
+
requirements: stableList(prior.requirements.map((requirement) => ({
|
|
20175
|
+
...requirement,
|
|
20176
|
+
scope: normalizedScope(requirement.scope)
|
|
20177
|
+
})))
|
|
20178
|
+
});
|
|
20179
|
+
var invalidFields = (error) => ({
|
|
20180
|
+
fields: error.issues.map((issue) => ({
|
|
20181
|
+
path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
|
|
20182
|
+
reason: issue.message
|
|
20183
|
+
}))
|
|
20184
|
+
});
|
|
20185
|
+
function normalizeResolverInput(input) {
|
|
20186
|
+
const parsed = resolverInputSchema.safeParse(input);
|
|
20187
|
+
if (!parsed.success)
|
|
20188
|
+
return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
|
|
20189
|
+
const value = parsed.data;
|
|
20190
|
+
const signals = value.assessment.signals;
|
|
20191
|
+
if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
|
|
20192
|
+
return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
|
|
20193
|
+
fields: [
|
|
20194
|
+
{
|
|
20195
|
+
path: "assessment.signals",
|
|
20196
|
+
reason: "contradictory behaviorChange and mechanicalLowRisk"
|
|
20197
|
+
}
|
|
20198
|
+
]
|
|
19798
20199
|
});
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19802
|
-
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
20200
|
+
const ids = value.constraints.map((constraint) => constraint.id);
|
|
20201
|
+
if (new Set(ids).size !== ids.length)
|
|
20202
|
+
return failure2("invalid_input", "constraint IDs must be unique", {
|
|
20203
|
+
fields: [{ path: "constraints", reason: "duplicate constraint id" }]
|
|
20204
|
+
});
|
|
20205
|
+
if (value.preferences?.fast && value.preferences.thorough)
|
|
20206
|
+
return failure2("invalid_input", "fast and thorough preferences contradict", {
|
|
20207
|
+
fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
|
|
20208
|
+
});
|
|
20209
|
+
const normalized = {
|
|
20210
|
+
intent: normalizeIntent(value.intent),
|
|
20211
|
+
assessment: normalizeAssessment(value.assessment),
|
|
20212
|
+
constraints: stableList(value.constraints.map(normalizeConstraint)),
|
|
20213
|
+
prior: normalizePrior(value.prior),
|
|
20214
|
+
...value.preferences ? { preferences: { ...value.preferences } } : {},
|
|
20215
|
+
policyVersion: POLICY_VERSION
|
|
20216
|
+
};
|
|
20217
|
+
return success(null, null, normalized);
|
|
19806
20218
|
}
|
|
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
|
-
|
|
20219
|
+
var requirement = (spec) => ({
|
|
20220
|
+
...spec,
|
|
20221
|
+
id: requirementId({
|
|
20222
|
+
ruleId: spec.ruleId,
|
|
20223
|
+
scope: spec.scope,
|
|
20224
|
+
satisfaction: spec.satisfaction,
|
|
20225
|
+
before: spec.before,
|
|
20226
|
+
dependentAction: spec.dependentAction
|
|
20227
|
+
})
|
|
20228
|
+
});
|
|
20229
|
+
var signalReason = (input, name) => input.assessment.signals[name].reason;
|
|
20230
|
+
function resolveRequirements(input) {
|
|
20231
|
+
const { signals } = input.assessment;
|
|
20232
|
+
const scope = input.intent.scope;
|
|
20233
|
+
const requirements = [];
|
|
20234
|
+
const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
|
|
20235
|
+
const hasUnknownFact = [
|
|
20236
|
+
...input.assessment.facts,
|
|
20237
|
+
...input.assessment.consequences.map(({ fact }) => fact)
|
|
20238
|
+
].some((fact) => fact.basis === "unknown");
|
|
20239
|
+
if (hasUnknownSignal || hasUnknownFact)
|
|
20240
|
+
requirements.push(requirement({
|
|
20241
|
+
ruleId: "consequential-unknown",
|
|
20242
|
+
dimension: "investigation",
|
|
20243
|
+
scope,
|
|
20244
|
+
reason: `Resolve consequential uncertainty before the dependent action: ${[
|
|
20245
|
+
...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
|
|
20246
|
+
...hasUnknownFact ? ["unknown fact"] : []
|
|
20247
|
+
].join(", ")}`,
|
|
20248
|
+
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
20249
|
+
before: "dependent_action",
|
|
20250
|
+
dependentAction: null,
|
|
20251
|
+
acceptanceAllowed: false
|
|
20252
|
+
}));
|
|
20253
|
+
if (signals.productChoiceOpen.value === true)
|
|
20254
|
+
requirements.push(requirement({
|
|
20255
|
+
ruleId: "product-decision",
|
|
20256
|
+
dimension: "decisions",
|
|
20257
|
+
scope,
|
|
20258
|
+
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
20259
|
+
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
20260
|
+
before: "dependent_action",
|
|
20261
|
+
dependentAction: null,
|
|
20262
|
+
acceptanceAllowed: false
|
|
20263
|
+
}));
|
|
20264
|
+
const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
|
|
20265
|
+
if (signals.behaviorChange.value === true)
|
|
20266
|
+
requirements.push(requirement({
|
|
20267
|
+
ruleId: "behavioral-verification",
|
|
20268
|
+
dimension: "testing",
|
|
20269
|
+
scope,
|
|
20270
|
+
reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
|
|
20271
|
+
satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
|
|
20272
|
+
before: "close",
|
|
20273
|
+
dependentAction: null,
|
|
20274
|
+
acceptanceAllowed: false
|
|
20275
|
+
}), requirement({
|
|
20276
|
+
ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
|
|
20277
|
+
dimension: "review",
|
|
20278
|
+
scope,
|
|
20279
|
+
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.",
|
|
20280
|
+
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.",
|
|
20281
|
+
before: "close",
|
|
20282
|
+
dependentAction: null,
|
|
20283
|
+
acceptanceAllowed: !independentBehaviorReview
|
|
20284
|
+
}));
|
|
20285
|
+
else if (signals.mechanicalLowRisk.value === true)
|
|
20286
|
+
requirements.push(requirement({
|
|
20287
|
+
ruleId: "mechanical-existing-checks",
|
|
20288
|
+
dimension: "verification",
|
|
20289
|
+
scope,
|
|
20290
|
+
reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
|
|
20291
|
+
satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
|
|
20292
|
+
before: "close",
|
|
20293
|
+
dependentAction: null,
|
|
20294
|
+
acceptanceAllowed: true
|
|
20295
|
+
}), requirement({
|
|
20296
|
+
ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
|
|
20297
|
+
dimension: "review",
|
|
20298
|
+
scope,
|
|
20299
|
+
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.",
|
|
20300
|
+
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.",
|
|
20301
|
+
before: "close",
|
|
20302
|
+
dependentAction: null,
|
|
20303
|
+
acceptanceAllowed: !input.preferences?.thorough
|
|
20304
|
+
}));
|
|
20305
|
+
if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
|
|
20306
|
+
requirements.push(requirement({
|
|
20307
|
+
ruleId: "pre-pr-cleanup",
|
|
20308
|
+
dimension: "verification",
|
|
20309
|
+
scope,
|
|
20310
|
+
reason: "Implementation output must be deslopped before it is delivered.",
|
|
20311
|
+
satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
|
|
20312
|
+
before: "dependent_action",
|
|
20313
|
+
dependentAction: "hosting.pull_request",
|
|
20314
|
+
acceptanceAllowed: true
|
|
20315
|
+
}));
|
|
20316
|
+
if (signals.durableAgreementNeeded.value === true)
|
|
20317
|
+
requirements.push(requirement({
|
|
20318
|
+
ruleId: "durable-spec",
|
|
20319
|
+
dimension: "artifacts",
|
|
20320
|
+
scope,
|
|
20321
|
+
reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
|
|
20322
|
+
satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
|
|
20323
|
+
before: "write",
|
|
20324
|
+
dependentAction: null,
|
|
20325
|
+
acceptanceAllowed: true
|
|
20326
|
+
}));
|
|
20327
|
+
if (signals.coordinationPlanNeeded.value === true)
|
|
20328
|
+
requirements.push(requirement({
|
|
20329
|
+
ruleId: "coordination-plan",
|
|
20330
|
+
dimension: "continuity",
|
|
20331
|
+
scope,
|
|
20332
|
+
reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
|
|
20333
|
+
satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
|
|
20334
|
+
before: "dependent_action",
|
|
20335
|
+
dependentAction: null,
|
|
20336
|
+
acceptanceAllowed: true
|
|
20337
|
+
}));
|
|
20338
|
+
if (signals.helperUseful.value === true && !input.preferences?.fast)
|
|
20339
|
+
requirements.push(requirement({
|
|
20340
|
+
ruleId: "helper-usefulness",
|
|
20341
|
+
dimension: "delegation",
|
|
20342
|
+
scope,
|
|
20343
|
+
reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
|
|
20344
|
+
satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
|
|
20345
|
+
before: "dependent_action",
|
|
20346
|
+
dependentAction: null,
|
|
20347
|
+
acceptanceAllowed: true
|
|
20348
|
+
}));
|
|
20349
|
+
for (const constraint of input.constraints)
|
|
20350
|
+
for (const obligation of constraint.requires) {
|
|
20351
|
+
const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
|
|
20352
|
+
requirements.push(requirement({
|
|
20353
|
+
ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
|
|
20354
|
+
kind: obligation.kind,
|
|
20355
|
+
scope: normalizedScope(obligation.scope),
|
|
20356
|
+
refs: normalizedRefList(obligation.refs),
|
|
20357
|
+
method: obligation.method,
|
|
20358
|
+
before: obligation.before,
|
|
20359
|
+
dependentAction: obligation.dependentAction
|
|
20360
|
+
})}`,
|
|
20361
|
+
dimension,
|
|
20362
|
+
scope: obligation.scope,
|
|
20363
|
+
reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
|
|
20364
|
+
satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
|
|
20365
|
+
before: obligation.before,
|
|
20366
|
+
dependentAction: obligation.dependentAction,
|
|
20367
|
+
acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
|
|
20368
|
+
}));
|
|
20369
|
+
}
|
|
20370
|
+
const seen = new Set;
|
|
20371
|
+
return requirements.filter((item) => {
|
|
20372
|
+
if (seen.has(item.id))
|
|
20373
|
+
return false;
|
|
20374
|
+
seen.add(item.id);
|
|
20375
|
+
return true;
|
|
20376
|
+
});
|
|
20377
|
+
}
|
|
20378
|
+
function resolvePolicy(input) {
|
|
20379
|
+
const normalized = normalizeResolverInput(input);
|
|
20380
|
+
if (!normalized.ok)
|
|
20381
|
+
return normalized;
|
|
20382
|
+
const requirements = resolveRequirements(normalized.data);
|
|
20383
|
+
const policy = {
|
|
20384
|
+
policyVersion: POLICY_VERSION,
|
|
20385
|
+
inputDigest: sha256(canonicalJson(normalized.data)),
|
|
20386
|
+
requirements
|
|
19874
20387
|
};
|
|
19875
|
-
|
|
19876
|
-
|
|
19877
|
-
|
|
19878
|
-
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
|
|
19885
|
-
|
|
19886
|
-
|
|
19887
|
-
|
|
19888
|
-
|
|
19889
|
-
|
|
19890
|
-
|
|
20388
|
+
const parsed = policySchema.safeParse(policy);
|
|
20389
|
+
if (!parsed.success)
|
|
20390
|
+
return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
|
|
20391
|
+
return success(null, null, parsed.data);
|
|
20392
|
+
}
|
|
20393
|
+
function diffPolicy(previous, next, reason, now) {
|
|
20394
|
+
if (!policySchema.safeParse(previous).success && previous !== null)
|
|
20395
|
+
throw new TypeError("previous policy is invalid");
|
|
20396
|
+
if (!policySchema.safeParse(next).success)
|
|
20397
|
+
throw new TypeError("next policy is invalid");
|
|
20398
|
+
const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
|
|
20399
|
+
const newIds = new Set(next.requirements.map((item) => item.id));
|
|
20400
|
+
const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits3);
|
|
20401
|
+
const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits3);
|
|
20402
|
+
if (added.length === 0 && retired.length === 0)
|
|
20403
|
+
return null;
|
|
20404
|
+
const normalizedReason = reason.trim();
|
|
20405
|
+
const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
|
|
20406
|
+
const change = {
|
|
20407
|
+
recordedAt: now,
|
|
20408
|
+
fromInputDigest: previous?.inputDigest ?? null,
|
|
20409
|
+
toInputDigest: next.inputDigest,
|
|
20410
|
+
added,
|
|
20411
|
+
retired,
|
|
20412
|
+
reason: changeReason
|
|
20413
|
+
};
|
|
20414
|
+
const parsed = policyChangeSchema.safeParse(change);
|
|
20415
|
+
if (!parsed.success)
|
|
20416
|
+
throw new TypeError("policy change is invalid");
|
|
20417
|
+
return parsed.data;
|
|
19891
20418
|
}
|
|
19892
|
-
var exportDigest = (bundle) => sha256({
|
|
19893
|
-
schemaVersion: bundle.schemaVersion,
|
|
19894
|
-
exportedAt: bundle.exportedAt,
|
|
19895
|
-
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
19896
|
-
task: bundle.task
|
|
19897
|
-
});
|
|
19898
20419
|
|
|
19899
20420
|
// packages/workit-core/src/core/task-engine.ts
|
|
19900
20421
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
@@ -21876,122 +22397,6 @@ class WorkitCore {
|
|
|
21876
22397
|
return this.summary(changed.data.task);
|
|
21877
22398
|
}
|
|
21878
22399
|
}
|
|
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
22400
|
// packages/workit-core/src/core.ts
|
|
21996
22401
|
var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
|
|
21997
22402
|
function gitRevisionParts(value) {
|
|
@@ -22035,7 +22440,7 @@ function resolveInside(root, candidate) {
|
|
|
22035
22440
|
}
|
|
22036
22441
|
function run2(root, executable, args, env = {}) {
|
|
22037
22442
|
const cwd = realpathSync7(root);
|
|
22038
|
-
const result =
|
|
22443
|
+
const result = spawnSync4(executable, args, {
|
|
22039
22444
|
cwd,
|
|
22040
22445
|
encoding: "utf8",
|
|
22041
22446
|
env: { ...process.env, ...env }
|
|
@@ -24495,9 +24900,9 @@ var applyPatterns = (value) => {
|
|
|
24495
24900
|
var redactSecrets = (value) => applyPatterns(value);
|
|
24496
24901
|
|
|
24497
24902
|
// packages/workit-core/src/core/pr-create.ts
|
|
24498
|
-
import
|
|
24903
|
+
import fs9 from "node:fs";
|
|
24499
24904
|
import path12 from "node:path";
|
|
24500
|
-
import { spawnSync as
|
|
24905
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
24501
24906
|
function parseGhIssue(value) {
|
|
24502
24907
|
const m = /issues\/(\d+)/.exec(value);
|
|
24503
24908
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
@@ -24510,7 +24915,7 @@ function whichOnPath(tool) {
|
|
|
24510
24915
|
for (const name of names) {
|
|
24511
24916
|
const candidate = path12.join(dir, name);
|
|
24512
24917
|
try {
|
|
24513
|
-
|
|
24918
|
+
fs9.accessSync(candidate, fs9.constants.X_OK);
|
|
24514
24919
|
return candidate;
|
|
24515
24920
|
} catch {}
|
|
24516
24921
|
}
|
|
@@ -24519,7 +24924,7 @@ function whichOnPath(tool) {
|
|
|
24519
24924
|
}
|
|
24520
24925
|
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
24521
24926
|
var pushRemote = (cwd) => {
|
|
24522
|
-
const result =
|
|
24927
|
+
const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
24523
24928
|
cwd,
|
|
24524
24929
|
encoding: "utf8"
|
|
24525
24930
|
});
|
|
@@ -24530,7 +24935,7 @@ var pushRemote = (cwd) => {
|
|
|
24530
24935
|
};
|
|
24531
24936
|
|
|
24532
24937
|
// packages/workit-core/src/core/repo-context.ts
|
|
24533
|
-
import { spawnSync as
|
|
24938
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
24534
24939
|
import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
|
|
24535
24940
|
import path13 from "node:path";
|
|
24536
24941
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
@@ -24559,7 +24964,7 @@ var invalidRange = (cwd) => ({
|
|
|
24559
24964
|
cwd
|
|
24560
24965
|
});
|
|
24561
24966
|
var runGit = (cwd, args) => {
|
|
24562
|
-
const result =
|
|
24967
|
+
const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
|
|
24563
24968
|
return {
|
|
24564
24969
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24565
24970
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24732,7 +25137,7 @@ var documentationFiles = (cwd) => {
|
|
|
24732
25137
|
walk(cwd, 1);
|
|
24733
25138
|
return matches.toSorted().slice(0, 200);
|
|
24734
25139
|
};
|
|
24735
|
-
var
|
|
25140
|
+
var readTrimmed2 = (file, maxLines) => {
|
|
24736
25141
|
if (!existsSync6(file))
|
|
24737
25142
|
return "";
|
|
24738
25143
|
const lines = readFileSync7(file, "utf8").split(`
|
|
@@ -24799,7 +25204,7 @@ ${status.stderr}` : "");
|
|
|
24799
25204
|
stdout += `template_path: ${template}
|
|
24800
25205
|
|
|
24801
25206
|
`;
|
|
24802
|
-
stdout +=
|
|
25207
|
+
stdout += readTrimmed2(path13.join(cwd, template), 220);
|
|
24803
25208
|
} else {
|
|
24804
25209
|
stdout += `template_path: none
|
|
24805
25210
|
|
|
@@ -24871,7 +25276,7 @@ function changelogContext(root, range) {
|
|
|
24871
25276
|
stdout += printSection("Existing CHANGELOG.md");
|
|
24872
25277
|
const changelogPath = path13.join(cwd, "CHANGELOG.md");
|
|
24873
25278
|
if (existsSync6(changelogPath)) {
|
|
24874
|
-
stdout +=
|
|
25279
|
+
stdout += readTrimmed2(changelogPath, 260) + `
|
|
24875
25280
|
`;
|
|
24876
25281
|
} else {
|
|
24877
25282
|
stdout += `CHANGELOG.md not found.
|
|
@@ -24913,7 +25318,7 @@ function docsRefreshContext(root, range) {
|
|
|
24913
25318
|
stdout += printSection("README Preview");
|
|
24914
25319
|
const readme = path13.join(cwd, "README.md");
|
|
24915
25320
|
if (existsSync6(readme)) {
|
|
24916
|
-
stdout +=
|
|
25321
|
+
stdout += readTrimmed2(readme, 220) + `
|
|
24917
25322
|
`;
|
|
24918
25323
|
} else {
|
|
24919
25324
|
stdout += `README.md not found.
|
|
@@ -25000,7 +25405,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
25000
25405
|
}
|
|
25001
25406
|
|
|
25002
25407
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
25003
|
-
import { spawnSync as
|
|
25408
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
25004
25409
|
var repoPathFromRemote = (remote) => {
|
|
25005
25410
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
25006
25411
|
if (!rest)
|
|
@@ -25021,7 +25426,7 @@ var defaultCli = (bin, available) => {
|
|
|
25021
25426
|
if (!available)
|
|
25022
25427
|
return null;
|
|
25023
25428
|
return (args, root) => {
|
|
25024
|
-
const out =
|
|
25429
|
+
const out = spawnSync7(bin, args, {
|
|
25025
25430
|
cwd: root,
|
|
25026
25431
|
encoding: "utf8",
|
|
25027
25432
|
env: { ...process.env, PATH: process.env.PATH ?? "" }
|
|
@@ -25099,7 +25504,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
25099
25504
|
};
|
|
25100
25505
|
|
|
25101
25506
|
// packages/workit-core/src/core/youtrack.ts
|
|
25102
|
-
import
|
|
25507
|
+
import fs10 from "node:fs";
|
|
25103
25508
|
import path14 from "node:path";
|
|
25104
25509
|
|
|
25105
25510
|
// packages/workit-core/src/core/templates.ts
|
|
@@ -25118,17 +25523,17 @@ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? p
|
|
|
25118
25523
|
var youTrackTokenModeOk = (p) => {
|
|
25119
25524
|
if (process.platform === "win32")
|
|
25120
25525
|
return true;
|
|
25121
|
-
const mode =
|
|
25526
|
+
const mode = fs10.statSync(p).mode & 511;
|
|
25122
25527
|
return mode === 384;
|
|
25123
25528
|
};
|
|
25124
25529
|
function readYouTrackConfig(required) {
|
|
25125
25530
|
const cfgPath = youTrackConfigPath();
|
|
25126
|
-
if (!
|
|
25531
|
+
if (!fs10.existsSync(cfgPath)) {
|
|
25127
25532
|
return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
|
|
25128
25533
|
}
|
|
25129
25534
|
let parsed;
|
|
25130
25535
|
try {
|
|
25131
|
-
parsed = JSON.parse(
|
|
25536
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25132
25537
|
} catch {
|
|
25133
25538
|
return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
|
|
25134
25539
|
}
|
|
@@ -25139,11 +25544,11 @@ function readYouTrackConfig(required) {
|
|
|
25139
25544
|
}
|
|
25140
25545
|
var readYouTrackContextConfig = () => {
|
|
25141
25546
|
const cfgPath = youTrackReadOnlyConfigPath();
|
|
25142
|
-
if (!
|
|
25547
|
+
if (!fs10.existsSync(cfgPath))
|
|
25143
25548
|
return { data: {} };
|
|
25144
25549
|
let parsed;
|
|
25145
25550
|
try {
|
|
25146
|
-
parsed = JSON.parse(
|
|
25551
|
+
parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
|
|
25147
25552
|
} catch {
|
|
25148
25553
|
return { error: "YouTrack configuration is unavailable" };
|
|
25149
25554
|
}
|
|
@@ -25183,11 +25588,11 @@ var youTrackToken = () => {
|
|
|
25183
25588
|
return loaded;
|
|
25184
25589
|
const tokenFile = String(loaded.config.tokenFile ?? "");
|
|
25185
25590
|
const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
|
|
25186
|
-
if (!tokenPath || !
|
|
25591
|
+
if (!tokenPath || !fs10.existsSync(tokenPath))
|
|
25187
25592
|
return { error: "missing youtrack.token" };
|
|
25188
25593
|
if (!youTrackTokenModeOk(tokenPath))
|
|
25189
25594
|
return { error: "youtrack.token mode must be 0600" };
|
|
25190
|
-
const token =
|
|
25595
|
+
const token = fs10.readFileSync(tokenPath, "utf8").trim();
|
|
25191
25596
|
if (!token)
|
|
25192
25597
|
return { error: "empty token file" };
|
|
25193
25598
|
if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
|
|
@@ -25252,9 +25657,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
|
|
|
25252
25657
|
} catch {
|
|
25253
25658
|
continue;
|
|
25254
25659
|
}
|
|
25255
|
-
if (!
|
|
25660
|
+
if (!fs10.existsSync(full))
|
|
25256
25661
|
continue;
|
|
25257
|
-
const text =
|
|
25662
|
+
const text = fs10.readFileSync(full, "utf8");
|
|
25258
25663
|
const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
|
|
25259
25664
|
if (m)
|
|
25260
25665
|
return m[1];
|
|
@@ -25797,123 +26202,166 @@ async function runStdioServer(host, contextProvider = defaultContextProvider(hos
|
|
|
25797
26202
|
await server.connect(new StdioServerTransport);
|
|
25798
26203
|
return server;
|
|
25799
26204
|
}
|
|
26205
|
+
// packages/workit-core/src/hooks/descriptor.ts
|
|
26206
|
+
var support = (descriptor, axis) => {
|
|
26207
|
+
if (axis.startsWith("event:"))
|
|
26208
|
+
return descriptor.events[axis.slice("event:".length)].support;
|
|
26209
|
+
if (axis === "shellPolicy.deny")
|
|
26210
|
+
return descriptor.shellPolicy.deny;
|
|
26211
|
+
if (axis === "stopControl" || axis === "shellAvailable")
|
|
26212
|
+
return descriptor[axis];
|
|
26213
|
+
const [group, key] = axis.split(".");
|
|
26214
|
+
return descriptor[group][key];
|
|
26215
|
+
};
|
|
26216
|
+
var usable = (value) => value === "native" || value === "partial";
|
|
26217
|
+
var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map((rule) => {
|
|
26218
|
+
const levels = rule.requires.map((axis) => support(descriptor, axis));
|
|
26219
|
+
const available = rule.assurance !== "unavailable" && levels.every(usable) && (rule.observed ?? []).every((flag) => observed[flag] === true);
|
|
26220
|
+
const assurance = !available ? "unavailable" : rule.assurance === "enforced" && (levels.length === 0 || levels.some((level) => level !== "native")) ? "agent_guided" : rule.assurance;
|
|
26221
|
+
return {
|
|
26222
|
+
name: rule.name,
|
|
26223
|
+
surface: rule.surface,
|
|
26224
|
+
assurance,
|
|
26225
|
+
reason: assurance === "unavailable" ? rule.unavailableReason ?? rule.reason : rule.reason,
|
|
26226
|
+
refs: rule.refs.map((handle) => ({ kind: "host", host: descriptor.host, handle }))
|
|
26227
|
+
};
|
|
26228
|
+
});
|
|
26229
|
+
// packages/workit-core/src/hooks/context.ts
|
|
26230
|
+
var cache = new Map;
|
|
26231
|
+
// packages/workit-core/src/hooks/handle.ts
|
|
26232
|
+
var offered = new Set;
|
|
26233
|
+
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
26234
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
|
|
25800
26235
|
|
|
25801
|
-
// packages/workit-
|
|
25802
|
-
var
|
|
26236
|
+
// packages/workit-core/src/hooks/hosts/claude-code.ts
|
|
26237
|
+
var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
|
|
26238
|
+
var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
26239
|
+
var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
|
|
26240
|
+
|
|
26241
|
+
// packages/workit-core/src/hooks/hosts/codex.ts
|
|
25803
26242
|
function detectCodexSurface(env) {
|
|
25804
26243
|
return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
|
|
25805
26244
|
}
|
|
25806
|
-
var
|
|
25807
|
-
|
|
25808
|
-
|
|
26245
|
+
var undocumented = { support: "undocumented", native: null };
|
|
26246
|
+
var CODEX_DESCRIPTOR = {
|
|
26247
|
+
host: "codex_cli",
|
|
26248
|
+
label: "Codex",
|
|
26249
|
+
verifiedAgainst: "codex-cli 0.153.4; Codex Desktop 26.901.20858",
|
|
26250
|
+
docs: ["https://developers.openai.com/codex/hooks"],
|
|
26251
|
+
transport: "hook-process",
|
|
26252
|
+
events: {
|
|
26253
|
+
"session.start": { support: "native", native: "SessionStart" },
|
|
26254
|
+
"context.turn": undocumented,
|
|
26255
|
+
"shell.pre": { support: "native", native: "PreToolUse" },
|
|
26256
|
+
"tool.pre": { support: "native", native: "PreToolUse" },
|
|
26257
|
+
"shell.post": undocumented,
|
|
26258
|
+
"subagent.start": { support: "native", native: "SubagentStart" },
|
|
26259
|
+
"subagent.stop": { support: "native", native: "SubagentStop" },
|
|
26260
|
+
"prompt.submit": undocumented,
|
|
26261
|
+
"compact.pre": { support: "none", native: null },
|
|
26262
|
+
stop: undocumented
|
|
26263
|
+
},
|
|
26264
|
+
shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
|
|
26265
|
+
context: {
|
|
26266
|
+
sessionStart: "native",
|
|
26267
|
+
perTurn: "undocumented",
|
|
26268
|
+
afterCompact: "native",
|
|
26269
|
+
task: "single-active"
|
|
26270
|
+
},
|
|
26271
|
+
subagents: {
|
|
26272
|
+
identity: "native",
|
|
26273
|
+
parentBinding: "none",
|
|
26274
|
+
blockStart: "none",
|
|
26275
|
+
worktreeIsolation: "undocumented",
|
|
26276
|
+
maxConcurrency: "undocumented"
|
|
26277
|
+
},
|
|
26278
|
+
provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
|
|
26279
|
+
interaction: { questions: "none", writeBoundary: "partial" },
|
|
26280
|
+
stopControl: "undocumented",
|
|
26281
|
+
shellAvailable: "native",
|
|
26282
|
+
perEventCost: "low",
|
|
26283
|
+
capabilities: [
|
|
25809
26284
|
{
|
|
25810
26285
|
name: "interactive_decision",
|
|
25811
26286
|
surface: "Question",
|
|
26287
|
+
refs: ["Question"],
|
|
26288
|
+
requires: [],
|
|
25812
26289
|
assurance: "agent_guided",
|
|
25813
|
-
reason: "Codex hooks expose no native arbitrary-question answer receipt"
|
|
25814
|
-
refs: [ref(host, "Question")]
|
|
26290
|
+
reason: "Codex hooks expose no native arbitrary-question answer receipt"
|
|
25815
26291
|
},
|
|
25816
26292
|
{
|
|
25817
26293
|
name: "known_product_writes",
|
|
25818
26294
|
surface: "PreToolUse",
|
|
26295
|
+
refs: ["PreToolUse"],
|
|
26296
|
+
requires: [],
|
|
25819
26297
|
assurance: "unavailable",
|
|
25820
|
-
reason: "file writes are host-policy; PreToolUse allows write tools"
|
|
25821
|
-
refs: [ref(host, "PreToolUse")]
|
|
26298
|
+
reason: "file writes are host-policy; PreToolUse allows write tools"
|
|
25822
26299
|
},
|
|
25823
26300
|
{
|
|
25824
26301
|
name: "fresh-context-review",
|
|
25825
26302
|
surface: "SubagentStart",
|
|
25826
|
-
|
|
25827
|
-
|
|
25828
|
-
|
|
26303
|
+
refs: ["SubagentStart"],
|
|
26304
|
+
requires: ["event:subagent.start"],
|
|
26305
|
+
observed: ["subagent.start"],
|
|
26306
|
+
assurance: "agent_guided",
|
|
26307
|
+
reason: "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted",
|
|
26308
|
+
unavailableReason: "Codex subagent lifecycle hooks are unavailable for independent review"
|
|
25829
26309
|
},
|
|
25830
26310
|
{
|
|
25831
26311
|
name: "native_subagents",
|
|
25832
26312
|
surface: "SubagentStart/SubagentStop",
|
|
25833
|
-
|
|
25834
|
-
|
|
25835
|
-
|
|
26313
|
+
refs: ["SubagentStart", "SubagentStop"],
|
|
26314
|
+
requires: ["event:subagent.start", "event:subagent.stop"],
|
|
26315
|
+
observed: ["subagent.start", "subagent.stop"],
|
|
26316
|
+
assurance: "agent_guided",
|
|
26317
|
+
reason: "Codex reports stable child identities, but cannot block creation or bind a writer",
|
|
26318
|
+
unavailableReason: "Codex subagent lifecycle hooks are untrusted or incomplete"
|
|
25836
26319
|
},
|
|
25837
26320
|
{
|
|
25838
26321
|
name: "native_subagent_start",
|
|
25839
26322
|
surface: "SubagentStart",
|
|
25840
|
-
|
|
25841
|
-
|
|
25842
|
-
|
|
26323
|
+
refs: ["SubagentStart"],
|
|
26324
|
+
requires: ["event:subagent.start"],
|
|
26325
|
+
observed: ["subagent.start"],
|
|
26326
|
+
assurance: "agent_guided",
|
|
26327
|
+
reason: "SubagentStart supplies identity and bounded read-only guidance; continue:false cannot stop creation",
|
|
26328
|
+
unavailableReason: "SubagentStart hook is untrusted or unavailable"
|
|
25843
26329
|
},
|
|
25844
26330
|
{
|
|
25845
26331
|
name: "arbitrary_shell_write",
|
|
25846
26332
|
surface: "unobservable_shell",
|
|
26333
|
+
refs: ["PreToolUse"],
|
|
26334
|
+
requires: [],
|
|
25847
26335
|
assurance: "unavailable",
|
|
25848
|
-
reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete"
|
|
25849
|
-
refs: [ref(host, "PreToolUse")]
|
|
26336
|
+
reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete"
|
|
25850
26337
|
},
|
|
25851
26338
|
{
|
|
25852
26339
|
name: "compact_context",
|
|
25853
26340
|
surface: "SessionStart",
|
|
25854
|
-
|
|
25855
|
-
|
|
25856
|
-
|
|
26341
|
+
refs: ["SessionStart"],
|
|
26342
|
+
requires: ["context.afterCompact"],
|
|
26343
|
+
observed: ["session.start"],
|
|
26344
|
+
assurance: "agent_guided",
|
|
26345
|
+
reason: "SessionStart source=compact is the single restore path"
|
|
25857
26346
|
}
|
|
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
|
-
])
|
|
26347
|
+
]
|
|
25915
26348
|
};
|
|
25916
|
-
var
|
|
26349
|
+
var codexDescriptor = (host) => ({ ...CODEX_DESCRIPTOR, host });
|
|
26350
|
+
var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
|
|
26351
|
+
var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
|
|
26352
|
+
|
|
26353
|
+
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
26354
|
+
var BLOCKING = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
|
|
26355
|
+
|
|
26356
|
+
// packages/workit-core/src/hooks/run.ts
|
|
26357
|
+
if (false) {}
|
|
26358
|
+
// packages/workit-codex/hooks/workit-hook.ts
|
|
26359
|
+
var codexCapabilities = (host, availability = {}) => capabilitiesFor(codexDescriptor(host), {
|
|
26360
|
+
"session.start": availability.sessionStart,
|
|
26361
|
+
"shell.pre": availability.preToolUse,
|
|
26362
|
+
"subagent.start": availability.subagentStart,
|
|
26363
|
+
"subagent.stop": availability.subagentStop
|
|
26364
|
+
});
|
|
25917
26365
|
if (false)
|
|
25918
26366
|
;
|
|
25919
26367
|
|