@brainervirus/workit-codex 2.1.5 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 spawnSync3 } from "node:child_process";
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(operationSchemas[family], { target: "draft-2020-12" });
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 fs4 from "node:fs";
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/task-store.ts
16026
- var processEvidenceSchema = object({
16027
- state: _enum(["stopped", "accounted_for"]),
16028
- pid: number2().int().nonnegative(),
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 = fs4.statSync(file, { bigint: true });
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(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
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 = fs4.statSync(normalizedLeft, { bigint: true });
16135
- const b = fs4.statSync(normalizedRight, { bigint: true });
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 = fs4.realpathSync(normalizedLeft);
16141
- const canonicalRight = fs4.realpathSync(normalizedRight);
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
- constructor(root) {
16154
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
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 (!fs4.existsSync(this.tasksDir))
16387
+ if (!fs5.existsSync(this.tasksDir))
16175
16388
  return success(null, null, []);
16176
16389
  let names;
16177
16390
  try {
16178
- names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
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 (!fs4.existsSync(this.tasksDir))
16420
+ if (!fs5.existsSync(this.tasksDir))
16208
16421
  return success(null, null, []);
16209
16422
  let names;
16210
16423
  try {
16211
- names = fs4.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
16424
+ names = fs5.readdirSync(this.tasksDir).filter((name) => name.endsWith(".json"));
16212
16425
  } catch (error) {
16213
16426
  return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
16214
16427
  path: this.tasksDir
@@ -16470,7 +16683,7 @@ class TaskStore {
16470
16683
  if (!current.data)
16471
16684
  return failure2("not_found", "workspace not found");
16472
16685
  if (current.data.revision !== expected)
16473
- return this.conflict(expected, current.data.revision);
16686
+ return this.workspaceConflict(expected, current.data.revision);
16474
16687
  const previousBytes = this.snapshotBytes(this.workspacePath);
16475
16688
  if (!previousBytes)
16476
16689
  return failure2("storage_error", "workspace snapshot disappeared during mutation");
@@ -16513,7 +16726,7 @@ class TaskStore {
16513
16726
  if (task.data.revision !== expected)
16514
16727
  return this.conflict(expected, task.data.revision);
16515
16728
  if (workspace.data.revision !== input.expectedWorkspaceRevision)
16516
- return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
16729
+ return this.workspaceConflict(input.expectedWorkspaceRevision, workspace.data.revision);
16517
16730
  const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
16518
16731
  const previousTaskBytes = this.snapshotBytes(this.taskPath(input.taskId));
16519
16732
  if (!previousWorkspaceBytes || !previousTaskBytes)
@@ -16576,12 +16789,12 @@ class TaskStore {
16576
16789
  });
16577
16790
  }
16578
16791
  recoveryCandidates() {
16579
- if (!fs4.existsSync(this.recoveryDir))
16792
+ if (!fs5.existsSync(this.recoveryDir))
16580
16793
  return success(null, null, []);
16581
16794
  try {
16582
16795
  const candidates = [];
16583
- for (const name of fs4.readdirSync(this.recoveryDir)) {
16584
- const match = /^(task|workspace)\.([^.]+)\.([0-9a-f]{64})\.json$/.exec(name);
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 = fs4.lstatSync(this.lockPath);
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 = fs4.readFileSync(this.lockPath, "utf8");
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: "fail-closed",
16753
- shouldReclaim: () => false,
16754
- parsePayload: parseMetadataLock,
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: this.processStart(process.pid),
16758
- host: hostname(),
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 = acquireFileLockSync(this.workspacePath, this.externalActionLockOptions());
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 = acquireFileLockSync(this.workspacePath, options);
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
- lockFailure(error) {
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 (code === "EEXIST" || code === "file_lock_timeout") {
16900
- const lock = this.readLockSnapshot();
16901
- if (lock.ok && lock.data?.data.externalAction)
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
- fs4.mkdirSync(this.tasksDir, { recursive: true });
16912
- fs4.mkdirSync(this.recoveryDir, { recursive: true });
16913
- fs4.writeFileSync(this.gitignorePath, `*
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 = fs4.openSync(temporary, "wx", 384);
17215
+ const fd = fs5.openSync(temporary, "wx", 384);
16927
17216
  try {
16928
- fs4.writeSync(fd, jsonBytes(value));
16929
- fs4.fsyncSync(fd);
17217
+ fs5.writeSync(fd, jsonBytes(value));
17218
+ fs5.fsyncSync(fd);
16930
17219
  } finally {
16931
- fs4.closeSync(fd);
17220
+ fs5.closeSync(fd);
16932
17221
  }
16933
- fs4.renameSync(temporary, file);
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
- fs4.unlinkSync(temporary);
17235
+ fs5.unlinkSync(temporary);
16947
17236
  } catch {}
16948
17237
  }
16949
17238
  }
16950
17239
  readIndex(workspaceId) {
16951
17240
  try {
16952
- const value = JSON.parse(fs4.readFileSync(this.indexPath, "utf8"));
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
- fs4.writeFileSync(temporary, JSON.stringify({
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
- fs4.renameSync(temporary, this.indexPath);
17261
+ retryTransient(() => fs5.renameSync(temporary, this.indexPath));
16973
17262
  } catch {
16974
17263
  try {
16975
- fs4.unlinkSync(temporary);
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 (fs4.existsSync(destination)) {
16994
- if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
16995
- return;
16996
- throw new Error("recovery copy already exists with different bytes");
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 = fs4.openSync(temporary, "wx", 384);
17293
+ const fd = fs5.openSync(temporary, "wx", 384);
17002
17294
  try {
17003
- fs4.writeSync(fd, bytes);
17004
- fs4.fsyncSync(fd);
17295
+ fs5.writeSync(fd, bytes);
17296
+ fs5.fsyncSync(fd);
17005
17297
  } finally {
17006
- fs4.closeSync(fd);
17298
+ fs5.closeSync(fd);
17007
17299
  }
17008
- fs4.renameSync(temporary, destination);
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
- fs4.unlinkSync(temporary);
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 (!fs4.existsSync(this.recoveryDir))
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 (!fs4.lstatSync(candidate.path).isFile())
17440
+ if (!fs5.lstatSync(candidate.path).isFile())
17032
17441
  continue;
17033
- const bytes = fs4.readFileSync(candidate.path);
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 = fs4.openSync(directory, "r");
17460
+ const fd = fs5.openSync(directory, "r");
17052
17461
  try {
17053
- fs4.fsyncSync(fd);
17462
+ fs5.fsyncSync(fd);
17054
17463
  } finally {
17055
- fs4.closeSync(fd);
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 (!fs4.existsSync(this.tasksDir))
17472
+ if (!fs5.existsSync(this.tasksDir))
17064
17473
  return null;
17065
17474
  const ids = new Set;
17066
17475
  try {
17067
- for (const name of fs4.readdirSync(this.tasksDir)) {
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(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
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 fs4.readFileSync(file);
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 = fs4.readFileSync(file, "utf8");
17498
+ const bytes = retryTransient(() => fs5.readFileSync(file, "utf8"));
17090
17499
  return { exists: true, result: this.parseBytes(bytes, schema) };
17091
17500
  } catch (error) {
17092
17501
  if (error?.code === "ENOENT")
@@ -17099,7 +17508,7 @@ class TaskStore {
17099
17508
  };
17100
17509
  }
17101
17510
  }
17102
- parseBytes(bytes, schema) {
17511
+ parseBytes(bytes, schema, mode = "read") {
17103
17512
  let value;
17104
17513
  try {
17105
17514
  value = JSON.parse(typeof bytes === "string" ? bytes : bytes.toString("utf8"));
@@ -17108,27 +17517,28 @@ class TaskStore {
17108
17517
  }
17109
17518
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
17110
17519
  return failure2("unsupported_version", "unsupported snapshot schema version");
17111
- const parsed = schema.safeParse(value);
17112
- if (parsed.success)
17520
+ const parsed = parseStoredRecord(schema, value);
17521
+ if (parsed.success && (mode === "read" || parsed.stripped.length === 0))
17113
17522
  return success(null, null, parsed.data);
17114
17523
  const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
17524
+ const upgrade = `upgrade Workit before ${parsed.success ? "recovering" : "mutating"} this checkout`;
17525
+ if (parsed.success)
17526
+ return failure2("recovery_required", `snapshot carries fields this Workit cannot preserve (${parsed.stripped.join(", ")}); ${upgrade}`);
17527
+ if (parsed.critical.length > 0)
17528
+ return failure2("recovery_required", `snapshot requires fields this Workit cannot read (${parsed.critical.join(", ")}); ${upgrade}`);
17115
17529
  if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
17116
17530
  return failure2("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
17117
17531
  return failure2("recovery_required", "snapshot does not satisfy its schema");
17118
17532
  }
17119
- processStart(pid) {
17120
- try {
17121
- return fs4.readFileSync(`/proc/${pid}/stat`, "utf8").split(" ")[21] ?? null;
17122
- } catch {
17123
- return null;
17124
- }
17125
- }
17126
17533
  conflict(expected, actual) {
17127
17534
  return failure2("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
17128
17535
  expectedRevision: expected,
17129
17536
  actualRevision: actual
17130
17537
  });
17131
17538
  }
17539
+ workspaceConflict(expected, actual) {
17540
+ return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
17541
+ }
17132
17542
  taskPath(taskId) {
17133
17543
  return path4.join(this.tasksDir, `${taskId}.json`);
17134
17544
  }
@@ -17154,53 +17564,116 @@ class TaskStore {
17154
17564
  return path4.join(this.workitDir, ".gitignore");
17155
17565
  }
17156
17566
  }
17157
- // packages/workit-core/src/core/task-engine.ts
17158
- import { realpathSync as realpathSync6 } from "node:fs";
17159
-
17160
- // packages/workit-core/src/core/authority.ts
17161
- import { createHash as createHash5 } from "node:crypto";
17162
- import * as fs6 from "node:fs";
17163
- import path9 from "node:path";
17164
-
17165
- // packages/workit-core/src/core/auto-approval.ts
17166
- import { createHash as createHash4 } from "node:crypto";
17167
- import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
17168
- import path8 from "node:path";
17169
-
17170
- // packages/workit-core/src/core/workspaces.ts
17171
- import { createHash as createHash3 } from "node:crypto";
17172
- import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
17173
- import path6 from "node:path";
17174
-
17175
- // packages/workit-core/src/core/config.ts
17176
- import {
17177
- copyFileSync,
17178
- cpSync,
17179
- existsSync as existsSync3,
17180
- mkdirSync as mkdirSync2,
17181
- readFileSync as readFileSync2,
17182
- readdirSync as readdirSync2,
17183
- writeFileSync as writeFileSync2
17184
- } from "node:fs";
17185
- import os from "node:os";
17186
- import path5 from "node:path";
17187
-
17188
- // packages/workit-core/src/core/boundary.ts
17189
- import { statSync as statSync2 } from "node:fs";
17190
- var EVENT = {
17191
- initialization: "initialization",
17192
- provenance: "provenance",
17193
- assets: "assets",
17194
- configurationSource: "configuration_source",
17195
- hooks: "hooks",
17196
- mcpConnection: "mcp_connection",
17197
- migration: "migration",
17198
- installSteps: "install_steps",
17199
- uncaughtFailure: "uncaught_failure",
17200
- toolsFailed: "tools_failed",
17201
- doctor: "doctor"
17567
+ // packages/workit-core/src/core/workers.ts
17568
+ var same = (left, right) => {
17569
+ try {
17570
+ return canonicalJson(left) === canonicalJson(right);
17571
+ } catch {
17572
+ return false;
17573
+ }
17202
17574
  };
17203
- var errorDetail = (err) => {
17575
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
17576
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
17577
+ var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
17578
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
17579
+ var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
17580
+ const code = char.charCodeAt(0);
17581
+ return code < 32 || code === 127;
17582
+ }) && !value.split(/[\\/]/).includes("..");
17583
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
17584
+ var workerIdOf = (value) => value.workerId ?? null;
17585
+ var currentWriterOwnsTask = (task, workspace, caller) => {
17586
+ const owner = workspace.writer?.owner;
17587
+ return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
17588
+ };
17589
+ function assertProductWriteAllowed(input) {
17590
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
17591
+ return failure2("invalid_input", "invalid product write path");
17592
+ if (!input.store)
17593
+ return failure2("permission_denied", "write authorization requires core state");
17594
+ const task = input.store.readTask(input.task.id);
17595
+ if (!task.ok)
17596
+ return task;
17597
+ const workspace = input.store.readWorkspace();
17598
+ if (!workspace.ok)
17599
+ return workspace;
17600
+ if (!workspace.data)
17601
+ return failure2("not_found", "workspace not found");
17602
+ const workspaceRecord = workspace.data;
17603
+ if (task.data.status !== "active")
17604
+ return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
17605
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
17606
+ return failure2("recovery_required", "task and workspace bindings are invalid");
17607
+ const writer = workspaceRecord.writer;
17608
+ if (!writer)
17609
+ return failure2("permission_denied", "checkout has no writer owner");
17610
+ if (writer.state === "uncertain")
17611
+ return failure2("recovery_required", "checkout writer requires recovery", {
17612
+ owner: writer.owner
17613
+ });
17614
+ const caller = callerValue(input.caller);
17615
+ const workerId = workerIdOf(input.caller);
17616
+ const owner = writer.owner;
17617
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
17618
+ return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
17619
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
17620
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
17621
+ return failure2("permission_denied", "only implementers can own product writes");
17622
+ if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
17623
+ return failure2("recovery_required", "worker session is not an active writer");
17624
+ return success(null, workspaceRecord.revision, owner);
17625
+ }
17626
+
17627
+ // packages/workit-core/src/core/task-evaluation.ts
17628
+ import { createHash as createHash6 } from "node:crypto";
17629
+ import * as fs8 from "node:fs";
17630
+ import { spawnSync as spawnSync3 } from "node:child_process";
17631
+ import path10 from "node:path";
17632
+
17633
+ // packages/workit-core/src/core/authority.ts
17634
+ import { createHash as createHash5 } from "node:crypto";
17635
+ import * as fs7 from "node:fs";
17636
+ import path9 from "node:path";
17637
+
17638
+ // packages/workit-core/src/core/auto-approval.ts
17639
+ import { createHash as createHash4 } from "node:crypto";
17640
+ import { readFileSync as readFileSync4, realpathSync as realpathSync3 } from "node:fs";
17641
+ import path8 from "node:path";
17642
+
17643
+ // packages/workit-core/src/core/workspaces.ts
17644
+ import { createHash as createHash3 } from "node:crypto";
17645
+ import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
17646
+ import path6 from "node:path";
17647
+
17648
+ // packages/workit-core/src/core/config.ts
17649
+ import {
17650
+ copyFileSync,
17651
+ cpSync,
17652
+ existsSync as existsSync3,
17653
+ mkdirSync as mkdirSync2,
17654
+ readFileSync as readFileSync2,
17655
+ readdirSync as readdirSync2,
17656
+ writeFileSync as writeFileSync2
17657
+ } from "node:fs";
17658
+ import os from "node:os";
17659
+ import path5 from "node:path";
17660
+
17661
+ // packages/workit-core/src/core/boundary.ts
17662
+ import { statSync as statSync2 } from "node:fs";
17663
+ var EVENT = {
17664
+ initialization: "initialization",
17665
+ provenance: "provenance",
17666
+ assets: "assets",
17667
+ configurationSource: "configuration_source",
17668
+ hooks: "hooks",
17669
+ mcpConnection: "mcp_connection",
17670
+ migration: "migration",
17671
+ installSteps: "install_steps",
17672
+ uncaughtFailure: "uncaught_failure",
17673
+ toolsFailed: "tools_failed",
17674
+ doctor: "doctor"
17675
+ };
17676
+ var errorDetail = (err) => {
17204
17677
  if (err instanceof Error) {
17205
17678
  return { error_name: err.name, error: err.message };
17206
17679
  }
@@ -17829,13 +18302,13 @@ var gitContext = (workspaceRoot, paths = []) => {
17829
18302
  };
17830
18303
 
17831
18304
  // packages/workit-core/src/core/vcs-config.ts
17832
- import fs5 from "node:fs";
18305
+ import fs6 from "node:fs";
17833
18306
  import path7 from "node:path";
17834
- import { spawnSync } from "node:child_process";
18307
+ import { spawnSync as spawnSync2 } from "node:child_process";
17835
18308
  var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path7.join(configDir(), "vcs.json");
17836
18309
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
17837
18310
  var remoteProvider = (cwd) => {
17838
- const r = spawnSync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
18311
+ const r = spawnSync2("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
17839
18312
  if (r.status !== 0)
17840
18313
  return null;
17841
18314
  const url = (r.stdout ?? "").trim();
@@ -17872,7 +18345,7 @@ var remoteEndpoint = (remote) => {
17872
18345
  var configuredSshEndpoint = (host) => {
17873
18346
  if (host.startsWith("-"))
17874
18347
  return null;
17875
- const resolved = spawnSync("ssh", ["-G", host], {
18348
+ const resolved = spawnSync2("ssh", ["-G", host], {
17876
18349
  encoding: "utf8",
17877
18350
  timeout: 5000,
17878
18351
  maxBuffer: 1024 * 1024,
@@ -17922,7 +18395,7 @@ var readVcsConfig = () => {
17922
18395
  const file = vcsConfigPath();
17923
18396
  let raw;
17924
18397
  try {
17925
- raw = fs5.readFileSync(file, "utf8");
18398
+ raw = fs6.readFileSync(file, "utf8");
17926
18399
  } catch {
17927
18400
  return { status: "missing", path: file, config: {} };
17928
18401
  }
@@ -18023,7 +18496,7 @@ function vcsCliIdentity(cwd) {
18023
18496
  PATH: process.env.PATH ?? "",
18024
18497
  ...provider === "github" ? { GH_HOST: host } : { GITLAB_HOST: host }
18025
18498
  };
18026
- const result = spawnSync(bin, ["api", "user"], {
18499
+ const result = spawnSync2(bin, ["api", "user"], {
18027
18500
  cwd: root,
18028
18501
  encoding: "utf8",
18029
18502
  env
@@ -18065,7 +18538,7 @@ function mergedPrStyle(limit = 6, cwd) {
18065
18538
  `).filter((l) => l.startsWith("## ")).map((l) => l.trim()),
18066
18539
  descriptionPreview: desc.slice(0, 600)
18067
18540
  });
18068
- const remoteResult = spawnSync("git", ["remote", "get-url", "--push", "--all", "origin"], {
18541
+ const remoteResult = spawnSync2("git", ["remote", "get-url", "--push", "--all", "origin"], {
18069
18542
  cwd,
18070
18543
  encoding: "utf8"
18071
18544
  });
@@ -18081,7 +18554,7 @@ function mergedPrStyle(limit = 6, cwd) {
18081
18554
  if (!hostingApiHostMatches(remote, host))
18082
18555
  return { ok: false, error: "PR style target does not match the provider host" };
18083
18556
  if (provider === "gitlab") {
18084
- const run = (args) => spawnSync("glab", ["api", ...args], {
18557
+ const run = (args) => spawnSync2("glab", ["api", ...args], {
18085
18558
  cwd,
18086
18559
  encoding: "utf8",
18087
18560
  env
@@ -18101,7 +18574,7 @@ function mergedPrStyle(limit = 6, cwd) {
18101
18574
  });
18102
18575
  }
18103
18576
  } else if (provider === "github") {
18104
- const r = spawnSync("gh", [
18577
+ const r = spawnSync2("gh", [
18105
18578
  "pr",
18106
18579
  "list",
18107
18580
  "--repo",
@@ -18160,66 +18633,6 @@ var verifyPushIdentity = (root, provider, account) => {
18160
18633
  return { ok: true };
18161
18634
  };
18162
18635
 
18163
- // packages/workit-core/src/core/workers.ts
18164
- var same = (left, right) => {
18165
- try {
18166
- return canonicalJson(left) === canonicalJson(right);
18167
- } catch {
18168
- return false;
18169
- }
18170
- };
18171
- var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
18172
- var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
18173
- var workerBlocksTransition = (transition, state) => transition === "pause" ? false : isUncertainWorker(state);
18174
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
18175
- var validPath = (value) => typeof value === "string" && value.length > 0 && !Array.from(value).some((char) => {
18176
- const code = char.charCodeAt(0);
18177
- return code < 32 || code === 127;
18178
- }) && !value.split(/[\\/]/).includes("..");
18179
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
18180
- var workerIdOf = (value) => value.workerId ?? null;
18181
- var currentWriterOwnsTask = (task, workspace, caller) => {
18182
- const owner = workspace.writer?.owner;
18183
- return task.status === "active" && task.workspaceId === workspace.id && owner?.taskId === task.id && owner.session.host === caller.host && owner.session.handle === caller.actor;
18184
- };
18185
- function assertProductWriteAllowed(input) {
18186
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
18187
- return failure2("invalid_input", "invalid product write path");
18188
- if (!input.store)
18189
- return failure2("permission_denied", "write authorization requires core state");
18190
- const task = input.store.readTask(input.task.id);
18191
- if (!task.ok)
18192
- return task;
18193
- const workspace = input.store.readWorkspace();
18194
- if (!workspace.ok)
18195
- return workspace;
18196
- if (!workspace.data)
18197
- return failure2("not_found", "workspace not found");
18198
- const workspaceRecord = workspace.data;
18199
- if (task.data.status !== "active")
18200
- return failure2("invalid_transition", "paused or closed tasks cannot own product writes");
18201
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
18202
- return failure2("recovery_required", "task and workspace bindings are invalid");
18203
- const writer = workspaceRecord.writer;
18204
- if (!writer)
18205
- return failure2("permission_denied", "checkout has no writer owner");
18206
- if (writer.state === "uncertain")
18207
- return failure2("recovery_required", "checkout writer requires recovery", {
18208
- owner: writer.owner
18209
- });
18210
- const caller = callerValue(input.caller);
18211
- const workerId = workerIdOf(input.caller);
18212
- const owner = writer.owner;
18213
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
18214
- return failure2("writer_conflict", "checkout is owned by another validated actor", { owner });
18215
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
18216
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
18217
- return failure2("permission_denied", "only implementers can own product writes");
18218
- if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
18219
- return failure2("recovery_required", "worker session is not an active writer");
18220
- return success(null, workspaceRecord.revision, owner);
18221
- }
18222
-
18223
18636
  // packages/workit-core/src/core/auto-approval.ts
18224
18637
  var AUTO_CLASSES = ["branch", "commit", "push", "pr", "merge"];
18225
18638
  var OPERATION_CLASS = {
@@ -18506,7 +18919,7 @@ var digestBytes2 = (bytes) => createHash5("sha256").update(bytes).digest("hex");
18506
18919
  var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
18507
18920
  let root;
18508
18921
  try {
18509
- root = fs6.realpathSync(checkoutRoot);
18922
+ root = fs7.realpathSync(checkoutRoot);
18510
18923
  } catch {
18511
18924
  return failure2("permission_denied", "checkout root is unavailable");
18512
18925
  }
@@ -18529,8 +18942,8 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
18529
18942
  if (!segment)
18530
18943
  continue;
18531
18944
  current = path9.join(current, segment);
18532
- const stat = fs6.lstatSync(current);
18533
- const real = fs6.realpathSync(current);
18945
+ const stat = fs7.lstatSync(current);
18946
+ const real = fs7.realpathSync(current);
18534
18947
  if (real !== root && !real.startsWith(`${root}${path9.sep}`))
18535
18948
  return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
18536
18949
  fields: [
@@ -18547,7 +18960,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
18547
18960
  if (current !== target && !stat.isDirectory())
18548
18961
  return failure2("invalid_input", "document reference ancestor is not a directory");
18549
18962
  }
18550
- if (digestBytes2(fs6.readFileSync(target)) !== reference.digest)
18963
+ if (digestBytes2(fs7.readFileSync(target)) !== reference.digest)
18551
18964
  return failure2("permission_denied", "approved document bytes have changed");
18552
18965
  } catch {
18553
18966
  return failure2("invalid_input", "approved document is unavailable");
@@ -18910,18 +19323,41 @@ function reconcileAction(input) {
18910
19323
  var bindingCovers = scopeCovers;
18911
19324
  var verifyDecisionContent = verifyContentRefs;
18912
19325
 
18913
- // packages/workit-core/src/core/policy-resolver.ts
18914
- var resolverInputSchema = object({
18915
- intent: intentSchema,
18916
- assessment: assessmentSchema,
18917
- constraints: array(constraintSchema),
18918
- prior: object({
18919
- decisions: array(decisionSchema),
18920
- findings: array(findingSchema),
18921
- requirements: array(requirementSchema)
18922
- }).strict(),
18923
- preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
18924
- }).strict();
19326
+ // packages/workit-core/src/core/task-evaluation.ts
19327
+ var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
19328
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
19329
+ var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
19330
+ var canonicalPath = (value) => {
19331
+ if (typeof value !== "string" || !value)
19332
+ return null;
19333
+ if (value.includes("\\") || value.split("/").includes(".."))
19334
+ return null;
19335
+ const normalized = path10.posix.normalize(value);
19336
+ if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
19337
+ return null;
19338
+ return normalized === "." ? "." : normalized.replace(/\/$/, "");
19339
+ };
19340
+ var canonicalScope = (scope) => {
19341
+ const paths = scope.paths.length ? scope.paths : ["."];
19342
+ const normalizedPaths = paths.map(canonicalPath);
19343
+ const normalizedExclusions = scope.exclusions.map(canonicalPath);
19344
+ if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
19345
+ return null;
19346
+ return {
19347
+ description: scope.description,
19348
+ paths: [...new Set(normalizedPaths)],
19349
+ exclusions: [...new Set(normalizedExclusions)]
19350
+ };
19351
+ };
19352
+ var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
19353
+ var scopeMatches = (value, scope) => {
19354
+ const normalized = canonicalScope(scope);
19355
+ if (!normalized)
19356
+ return false;
19357
+ const paths = normalized.paths;
19358
+ return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
19359
+ };
19360
+ var pathRelevant = (value, scope) => scopeMatches(value, scope);
18925
19361
  var compareCodeUnits2 = (left, right) => {
18926
19362
  const length = Math.min(left.length, right.length);
18927
19363
  for (let index = 0;index < length; index += 1) {
@@ -18931,362 +19367,8 @@ var compareCodeUnits2 = (left, right) => {
18931
19367
  }
18932
19368
  return left.length - right.length;
18933
19369
  };
18934
- var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits2);
18935
- var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits2(canonicalJson(left), canonicalJson(right)));
18936
- var normalizedScope = (scope) => ({
18937
- description: scope.description,
18938
- paths: stableUnique(scope.paths),
18939
- exclusions: stableUnique(scope.exclusions)
18940
- });
18941
- var normalizedRefList = (values) => stableList(values);
18942
- var normalizeAssessment = (assessment) => ({
18943
- facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
18944
- signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
18945
- name,
18946
- { ...signal, refs: normalizedRefList(signal.refs) }
18947
- ])),
18948
- consequences: stableList(assessment.consequences.map((consequence) => ({
18949
- ...consequence,
18950
- fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
18951
- }))),
18952
- verification: stableList(assessment.verification.map((verification) => ({
18953
- ...verification,
18954
- scope: normalizedScope(verification.scope),
18955
- availableChecks: normalizedRefList(verification.availableChecks),
18956
- gaps: stableUnique(verification.gaps)
18957
- })))
18958
- });
18959
- var normalizeConstraint = (constraint) => ({
18960
- ...constraint,
18961
- source: constraint.source,
18962
- requires: stableList(constraint.requires.map((requirement) => ({
18963
- ...requirement,
18964
- scope: normalizedScope(requirement.scope),
18965
- refs: normalizedRefList(requirement.refs)
18966
- })))
18967
- });
18968
- var normalizeDecision = (decision) => ({
18969
- ...decision,
18970
- binding: {
18971
- ...decision.binding,
18972
- scope: normalizedScope(decision.binding.scope),
18973
- contentRefs: normalizedRefList(decision.binding.contentRefs)
18974
- },
18975
- requirementIds: stableUnique(decision.requirementIds)
18976
- });
18977
- var normalizeIntent = (intent) => ({
18978
- ...intent,
18979
- scope: normalizedScope(intent.scope),
18980
- authorityRefs: normalizedRefList(intent.authorityRefs)
18981
- });
18982
- var normalizePrior = (prior) => ({
18983
- decisions: stableList(prior.decisions.map(normalizeDecision)),
18984
- findings: stableList(prior.findings.map((finding) => ({
18985
- ...finding,
18986
- scope: normalizedScope(finding.scope),
18987
- refs: normalizedRefList(finding.refs),
18988
- resolution: finding.resolution ? {
18989
- ...finding.resolution,
18990
- evidenceIds: stableUnique(finding.resolution.evidenceIds),
18991
- decisionIds: stableUnique(finding.resolution.decisionIds)
18992
- } : null
18993
- }))),
18994
- requirements: stableList(prior.requirements.map((requirement) => ({
18995
- ...requirement,
18996
- scope: normalizedScope(requirement.scope)
18997
- })))
18998
- });
18999
- var invalidFields = (error) => ({
19000
- fields: error.issues.map((issue) => ({
19001
- path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
19002
- reason: issue.message
19003
- }))
19004
- });
19005
- function normalizeResolverInput(input) {
19006
- const parsed = resolverInputSchema.safeParse(input);
19007
- if (!parsed.success)
19008
- return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
19009
- const value = parsed.data;
19010
- const signals = value.assessment.signals;
19011
- if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
19012
- return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
19013
- fields: [
19014
- {
19015
- path: "assessment.signals",
19016
- reason: "contradictory behaviorChange and mechanicalLowRisk"
19017
- }
19018
- ]
19019
- });
19020
- const ids = value.constraints.map((constraint) => constraint.id);
19021
- if (new Set(ids).size !== ids.length)
19022
- return failure2("invalid_input", "constraint IDs must be unique", {
19023
- fields: [{ path: "constraints", reason: "duplicate constraint id" }]
19024
- });
19025
- if (value.preferences?.fast && value.preferences.thorough)
19026
- return failure2("invalid_input", "fast and thorough preferences contradict", {
19027
- fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
19028
- });
19029
- const normalized = {
19030
- intent: normalizeIntent(value.intent),
19031
- assessment: normalizeAssessment(value.assessment),
19032
- constraints: stableList(value.constraints.map(normalizeConstraint)),
19033
- prior: normalizePrior(value.prior),
19034
- ...value.preferences ? { preferences: { ...value.preferences } } : {},
19035
- policyVersion: POLICY_VERSION
19036
- };
19037
- return success(null, null, normalized);
19038
- }
19039
- var requirement = (spec) => ({
19040
- ...spec,
19041
- id: requirementId({
19042
- ruleId: spec.ruleId,
19043
- scope: spec.scope,
19044
- satisfaction: spec.satisfaction,
19045
- before: spec.before,
19046
- dependentAction: spec.dependentAction
19047
- })
19048
- });
19049
- var signalReason = (input, name) => input.assessment.signals[name].reason;
19050
- function resolveRequirements(input) {
19051
- const { signals } = input.assessment;
19052
- const scope = input.intent.scope;
19053
- const requirements = [];
19054
- const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
19055
- const hasUnknownFact = [
19056
- ...input.assessment.facts,
19057
- ...input.assessment.consequences.map(({ fact }) => fact)
19058
- ].some((fact) => fact.basis === "unknown");
19059
- if (hasUnknownSignal || hasUnknownFact)
19060
- requirements.push(requirement({
19061
- ruleId: "consequential-unknown",
19062
- dimension: "investigation",
19063
- scope,
19064
- reason: `Resolve consequential uncertainty before the dependent action: ${[
19065
- ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
19066
- ...hasUnknownFact ? ["unknown fact"] : []
19067
- ].join(", ")}`,
19068
- satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
19069
- before: "dependent_action",
19070
- dependentAction: null,
19071
- acceptanceAllowed: false
19072
- }));
19073
- if (signals.productChoiceOpen.value === true)
19074
- requirements.push(requirement({
19075
- ruleId: "product-decision",
19076
- dimension: "decisions",
19077
- scope,
19078
- reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
19079
- satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
19080
- before: "dependent_action",
19081
- dependentAction: null,
19082
- acceptanceAllowed: false
19083
- }));
19084
- const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
19085
- if (signals.behaviorChange.value === true)
19086
- requirements.push(requirement({
19087
- ruleId: "behavioral-verification",
19088
- dimension: "testing",
19089
- scope,
19090
- reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
19091
- satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
19092
- before: "close",
19093
- dependentAction: null,
19094
- acceptanceAllowed: false
19095
- }), requirement({
19096
- ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
19097
- dimension: "review",
19098
- scope,
19099
- reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
19100
- satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
19101
- before: "close",
19102
- dependentAction: null,
19103
- acceptanceAllowed: !independentBehaviorReview
19104
- }));
19105
- else if (signals.mechanicalLowRisk.value === true)
19106
- requirements.push(requirement({
19107
- ruleId: "mechanical-existing-checks",
19108
- dimension: "verification",
19109
- scope,
19110
- reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
19111
- satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
19112
- before: "close",
19113
- dependentAction: null,
19114
- acceptanceAllowed: true
19115
- }), requirement({
19116
- ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
19117
- dimension: "review",
19118
- scope,
19119
- reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
19120
- satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
19121
- before: "close",
19122
- dependentAction: null,
19123
- acceptanceAllowed: !input.preferences?.thorough
19124
- }));
19125
- if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
19126
- requirements.push(requirement({
19127
- ruleId: "pre-pr-cleanup",
19128
- dimension: "verification",
19129
- scope,
19130
- reason: "Implementation output must be deslopped before it is delivered.",
19131
- satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
19132
- before: "dependent_action",
19133
- dependentAction: "hosting.pull_request",
19134
- acceptanceAllowed: true
19135
- }));
19136
- if (signals.durableAgreementNeeded.value === true)
19137
- requirements.push(requirement({
19138
- ruleId: "durable-spec",
19139
- dimension: "artifacts",
19140
- scope,
19141
- reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
19142
- satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
19143
- before: "write",
19144
- dependentAction: null,
19145
- acceptanceAllowed: true
19146
- }));
19147
- if (signals.coordinationPlanNeeded.value === true)
19148
- requirements.push(requirement({
19149
- ruleId: "coordination-plan",
19150
- dimension: "continuity",
19151
- scope,
19152
- reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
19153
- satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
19154
- before: "dependent_action",
19155
- dependentAction: null,
19156
- acceptanceAllowed: true
19157
- }));
19158
- if (signals.helperUseful.value === true && !input.preferences?.fast)
19159
- requirements.push(requirement({
19160
- ruleId: "helper-usefulness",
19161
- dimension: "delegation",
19162
- scope,
19163
- reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
19164
- satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
19165
- before: "dependent_action",
19166
- dependentAction: null,
19167
- acceptanceAllowed: true
19168
- }));
19169
- for (const constraint of input.constraints)
19170
- for (const obligation of constraint.requires) {
19171
- const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
19172
- requirements.push(requirement({
19173
- ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
19174
- kind: obligation.kind,
19175
- scope: normalizedScope(obligation.scope),
19176
- refs: normalizedRefList(obligation.refs),
19177
- method: obligation.method,
19178
- before: obligation.before,
19179
- dependentAction: obligation.dependentAction
19180
- })}`,
19181
- dimension,
19182
- scope: obligation.scope,
19183
- reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
19184
- satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
19185
- before: obligation.before,
19186
- dependentAction: obligation.dependentAction,
19187
- acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
19188
- }));
19189
- }
19190
- const seen = new Set;
19191
- return requirements.filter((item) => {
19192
- if (seen.has(item.id))
19193
- return false;
19194
- seen.add(item.id);
19195
- return true;
19196
- });
19197
- }
19198
- function resolvePolicy(input) {
19199
- const normalized = normalizeResolverInput(input);
19200
- if (!normalized.ok)
19201
- return normalized;
19202
- const requirements = resolveRequirements(normalized.data);
19203
- const policy = {
19204
- policyVersion: POLICY_VERSION,
19205
- inputDigest: sha256(canonicalJson(normalized.data)),
19206
- requirements
19207
- };
19208
- const parsed = policySchema.safeParse(policy);
19209
- if (!parsed.success)
19210
- return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
19211
- return success(null, null, parsed.data);
19212
- }
19213
- function diffPolicy(previous, next, reason, now) {
19214
- if (!policySchema.safeParse(previous).success && previous !== null)
19215
- throw new TypeError("previous policy is invalid");
19216
- if (!policySchema.safeParse(next).success)
19217
- throw new TypeError("next policy is invalid");
19218
- const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
19219
- const newIds = new Set(next.requirements.map((item) => item.id));
19220
- const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits2);
19221
- const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits2);
19222
- if (added.length === 0 && retired.length === 0)
19223
- return null;
19224
- const normalizedReason = reason.trim();
19225
- const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
19226
- const change = {
19227
- recordedAt: now,
19228
- fromInputDigest: previous?.inputDigest ?? null,
19229
- toInputDigest: next.inputDigest,
19230
- added,
19231
- retired,
19232
- reason: changeReason
19233
- };
19234
- const parsed = policyChangeSchema.safeParse(change);
19235
- if (!parsed.success)
19236
- throw new TypeError("policy change is invalid");
19237
- return parsed.data;
19238
- }
19239
-
19240
- // packages/workit-core/src/core/task-evaluation.ts
19241
- import { createHash as createHash6 } from "node:crypto";
19242
- import * as fs7 from "node:fs";
19243
- import { spawnSync as spawnSync2 } from "node:child_process";
19244
- import path10 from "node:path";
19245
- var digestBytes3 = (value) => createHash6("sha256").update(value).digest("hex");
19246
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path10.sep}`);
19247
- var relative = (root, candidate) => path10.relative(root, candidate).split(path10.sep).join("/") || ".";
19248
- var canonicalPath = (value) => {
19249
- if (typeof value !== "string" || !value)
19250
- return null;
19251
- if (value.includes("\\") || value.split("/").includes(".."))
19252
- return null;
19253
- const normalized = path10.posix.normalize(value);
19254
- if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
19255
- return null;
19256
- return normalized === "." ? "." : normalized.replace(/\/$/, "");
19257
- };
19258
- var canonicalScope = (scope) => {
19259
- const paths = scope.paths.length ? scope.paths : ["."];
19260
- const normalizedPaths = paths.map(canonicalPath);
19261
- const normalizedExclusions = scope.exclusions.map(canonicalPath);
19262
- if (normalizedPaths.some((value) => value === null) || normalizedExclusions.some((value) => value === null))
19263
- return null;
19264
- return {
19265
- description: scope.description,
19266
- paths: [...new Set(normalizedPaths)],
19267
- exclusions: [...new Set(normalizedExclusions)]
19268
- };
19269
- };
19270
- var excluded = (value, exclusions) => exclusions.some((item) => item === value || item !== "." && value.startsWith(`${item}/`));
19271
- var scopeMatches = (value, scope) => {
19272
- const normalized = canonicalScope(scope);
19273
- if (!normalized)
19274
- return false;
19275
- const paths = normalized.paths;
19276
- return paths.some((item) => item === "." || value === item || value.startsWith(`${item}/`)) && !excluded(value, normalized.exclusions);
19277
- };
19278
- var pathRelevant = (value, scope) => scopeMatches(value, scope);
19279
- var compareCodeUnits3 = (left, right) => {
19280
- const length = Math.min(left.length, right.length);
19281
- for (let index = 0;index < length; index += 1) {
19282
- const difference = left.charCodeAt(index) - right.charCodeAt(index);
19283
- if (difference !== 0)
19284
- return difference;
19285
- }
19286
- return left.length - right.length;
19287
- };
19288
19370
  var gitPaths = (root) => {
19289
- const result = spawnSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
19371
+ const result = spawnSync3("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
19290
19372
  cwd: root,
19291
19373
  encoding: "buffer"
19292
19374
  });
@@ -19299,7 +19381,7 @@ var gitPaths = (root) => {
19299
19381
  };
19300
19382
  }
19301
19383
  const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path10.sep).join("/")) : [];
19302
- const stagedDeleted = spawnSync2("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
19384
+ const stagedDeleted = spawnSync3("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
19303
19385
  if (stagedDeleted.status !== 0) {
19304
19386
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
19305
19387
  if (!/not a git repository/i.test(stderr))
@@ -19312,7 +19394,7 @@ var gitPaths = (root) => {
19312
19394
  var walk = (root, directory, output) => {
19313
19395
  let names;
19314
19396
  try {
19315
- names = fs7.readdirSync(directory);
19397
+ names = fs8.readdirSync(directory);
19316
19398
  } catch {
19317
19399
  return true;
19318
19400
  }
@@ -19323,7 +19405,7 @@ var walk = (root, directory, output) => {
19323
19405
  const target = path10.join(directory, name);
19324
19406
  let stat;
19325
19407
  try {
19326
- stat = fs7.lstatSync(target);
19408
+ stat = fs8.lstatSync(target);
19327
19409
  } catch {
19328
19410
  uncertain = true;
19329
19411
  continue;
@@ -19342,10 +19424,10 @@ var scopeRoots = (root, scope) => {
19342
19424
  if (!inside(root, target))
19343
19425
  return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
19344
19426
  let ancestor = target;
19345
- while (!fs7.existsSync(ancestor) && ancestor !== root)
19427
+ while (!fs8.existsSync(ancestor) && ancestor !== root)
19346
19428
  ancestor = path10.dirname(ancestor);
19347
19429
  try {
19348
- if (!inside(root, fs7.realpathSync(ancestor)))
19430
+ if (!inside(root, fs8.realpathSync(ancestor)))
19349
19431
  return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
19350
19432
  } catch {
19351
19433
  return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
@@ -19357,7 +19439,7 @@ var scopeRoots = (root, scope) => {
19357
19439
  function captureCandidate(root, scope, environment = []) {
19358
19440
  let checkout;
19359
19441
  try {
19360
- checkout = fs7.realpathSync(root);
19442
+ checkout = fs8.realpathSync(root);
19361
19443
  } catch {
19362
19444
  return failure2("invalid_input", "candidate root does not exist");
19363
19445
  }
@@ -19375,11 +19457,11 @@ function captureCandidate(root, scope, environment = []) {
19375
19457
  if (item === ".git" || item === ".workit")
19376
19458
  continue;
19377
19459
  names.add(item);
19378
- if (!fs7.existsSync(target))
19460
+ if (!fs8.existsSync(target))
19379
19461
  continue;
19380
19462
  let stat;
19381
19463
  try {
19382
- stat = fs7.lstatSync(target);
19464
+ stat = fs8.lstatSync(target);
19383
19465
  } catch {
19384
19466
  uncertain = true;
19385
19467
  continue;
@@ -19397,15 +19479,15 @@ function captureCandidate(root, scope, environment = []) {
19397
19479
  continue;
19398
19480
  const target = path10.join(checkout, item);
19399
19481
  try {
19400
- const stat = fs7.lstatSync(target);
19482
+ const stat = fs8.lstatSync(target);
19401
19483
  if (stat.isSymbolicLink()) {
19402
- const link = fs7.readlinkSync(target);
19484
+ const link = fs8.readlinkSync(target);
19403
19485
  files.push({ path: item, kind: "symlink", digest: digestBytes3(link), executable: null });
19404
19486
  } else if (stat.isFile()) {
19405
19487
  files.push({
19406
19488
  path: item,
19407
19489
  kind: "file",
19408
- digest: digestBytes3(fs7.readFileSync(target)),
19490
+ digest: digestBytes3(fs8.readFileSync(target)),
19409
19491
  executable: (stat.mode & 73) !== 0
19410
19492
  });
19411
19493
  } else if (!stat.isDirectory()) {
@@ -19427,10 +19509,10 @@ function captureCandidate(root, scope, environment = []) {
19427
19509
  const namesByEnvironment = supplied.map(({ name }) => name);
19428
19510
  if (new Set(namesByEnvironment).size !== namesByEnvironment.length || namesByEnvironment.some((name) => typeof name !== "string" || !name))
19429
19511
  return failure2("invalid_input", "candidate environment names must be unique and non-empty");
19430
- const values = supplied.toSorted((left, right) => compareCodeUnits3(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
19512
+ const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
19431
19513
  if (values.some(({ value }) => value === null))
19432
19514
  completeness = "uncertain";
19433
- const headResult = spawnSync2("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
19515
+ const headResult = spawnSync3("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
19434
19516
  if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
19435
19517
  completeness = "uncertain";
19436
19518
  const head = headResult.status === 0 ? headResult.stdout.trim() : null;
@@ -19735,166 +19817,608 @@ function evaluateClosure(requestedOutcome, view) {
19735
19817
  });
19736
19818
  }
19737
19819
 
19738
- // packages/workit-core/src/core/methods.ts
19739
- var METHODS = {
19740
- "workit-challenge": { dimensions: ["challenge", "decisions"] },
19741
- "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
19742
- "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
19743
- "workit-plan": { dimensions: ["artifacts", "continuity"] },
19744
- "workit-implement": { dimensions: ["delegation"] },
19745
- "workit-debug": {},
19746
- "workit-handoff": {},
19747
- "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
19748
- };
19749
- var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
19750
- var assuranceFor = (id, definition, capabilities) => {
19751
- const keys = new Set([
19752
- id,
19753
- ...definition.dimensions ?? [],
19754
- ...definition.ruleIds ?? []
19755
- ]);
19756
- const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
19757
- if (id === "workit-review" && matches.length === 0)
19758
- return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
19759
- if (matches.some((capability) => capability.assurance === "unavailable"))
19760
- return {
19761
- assurance: "unavailable",
19762
- reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
19763
- };
19764
- if (matches.some((capability) => capability.assurance === "enforced"))
19765
- return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
19766
- return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
19820
+ // packages/workit-core/src/core/methods.ts
19821
+ var METHODS = {
19822
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
19823
+ "workit-behavioral-tdd": { dimensions: ["testing"], ruleIds: ["mechanical-existing-checks"] },
19824
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review", "self-review"] },
19825
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
19826
+ "workit-implement": { dimensions: ["delegation"] },
19827
+ "workit-debug": {},
19828
+ "workit-handoff": {},
19829
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
19830
+ };
19831
+ var methodMatches = (definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true || definition.ruleIds?.includes(requirement.ruleId) === true;
19832
+ var assuranceFor = (id, definition, capabilities) => {
19833
+ const keys = new Set([
19834
+ id,
19835
+ ...definition.dimensions ?? [],
19836
+ ...definition.ruleIds ?? []
19837
+ ]);
19838
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
19839
+ if (id === "workit-review" && matches.length === 0)
19840
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
19841
+ if (matches.some((capability) => capability.assurance === "unavailable"))
19842
+ return {
19843
+ assurance: "unavailable",
19844
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
19845
+ };
19846
+ if (matches.some((capability) => capability.assurance === "enforced"))
19847
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
19848
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
19849
+ };
19850
+ function selectMethods(policy, capabilities) {
19851
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
19852
+ const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
19853
+ if (requirements.length === 0)
19854
+ return [];
19855
+ const capability = assuranceFor(id, definition, capabilities);
19856
+ const reasons = [
19857
+ ...new Set(requirements.map((requirement) => requirement.reason)),
19858
+ ...capability.reasons
19859
+ ];
19860
+ if (capability.assurance === "unavailable")
19861
+ reasons.unshift("required capability is unavailable");
19862
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
19863
+ });
19864
+ }
19865
+
19866
+ // packages/workit-core/src/core/task-context.ts
19867
+ function reconcileResume(view, observations = []) {
19868
+ if (observations.length > 0)
19869
+ return failure2("permission_denied", "worker observations require native host verification");
19870
+ const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
19871
+ if (!candidate.ok)
19872
+ return candidate;
19873
+ const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
19874
+ const blockers = [...view.task.progress.blockers];
19875
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
19876
+ blockers.push({
19877
+ reason: "worker state requires reconciliation",
19878
+ dependentAction: "resume",
19879
+ refs: []
19880
+ });
19881
+ return success(null, null, {
19882
+ candidate: candidate.data,
19883
+ staleEvidenceIds,
19884
+ workerUpdates: [],
19885
+ blockers,
19886
+ reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
19887
+ });
19888
+ }
19889
+ var COMPACT_MAX_BYTES = 4096;
19890
+ var COMPACT_MAX_ITEMS = 8;
19891
+ var COMPACT_TEXT_BYTES = 128;
19892
+ var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
19893
+ var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
19894
+ var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
19895
+ var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
19896
+ const separator = match.search(/[:=]/u);
19897
+ if (separator >= 0)
19898
+ return `${match.slice(0, separator)}=<redacted>`;
19899
+ const space = match.search(/\s/u);
19900
+ return `${match.slice(0, space)} <redacted>`;
19901
+ }).slice(0, limit);
19902
+ var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
19903
+ var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
19904
+ var compactDecisionChoice = (entry) => {
19905
+ const stated = entry.data.binding.statedChoice?.text;
19906
+ const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
19907
+ const source = stated?.trim() || approved.trim() || null;
19908
+ return compactText(source, COMPACT_TEXT_BYTES);
19909
+ };
19910
+ var compactReference = (ref) => {
19911
+ if (ref.kind === "file")
19912
+ return {
19913
+ kind: "file",
19914
+ path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
19915
+ };
19916
+ if (ref.kind === "record")
19917
+ return {
19918
+ kind: "record",
19919
+ collection: ref.collection,
19920
+ id: ref.id
19921
+ };
19922
+ return { kind: ref.kind };
19923
+ };
19924
+ function compactTaskContext(view) {
19925
+ const decisions = view.task.decisions.slice().toSorted((left, right) => {
19926
+ const leftAt = compactTimestamp(left.recordedAt);
19927
+ const rightAt = compactTimestamp(right.recordedAt);
19928
+ if (leftAt !== rightAt)
19929
+ return leftAt > rightAt ? -1 : 1;
19930
+ return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
19931
+ }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
19932
+ id: entry.id,
19933
+ purpose: entry.data.purpose,
19934
+ status: entry.data.response,
19935
+ digest: entry.data.digest,
19936
+ choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
19937
+ references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
19938
+ }));
19939
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
19940
+ id: method.id,
19941
+ assurance: method.assurance,
19942
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
19943
+ })) : [];
19944
+ const gaps = Array.from(new Set([
19945
+ ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
19946
+ ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
19947
+ ...view.task.progress.blockers.map((entry) => entry.reason)
19948
+ ])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
19949
+ const context = {
19950
+ objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
19951
+ status: view.task.status,
19952
+ decisions,
19953
+ methods,
19954
+ gaps,
19955
+ nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
19956
+ };
19957
+ let encoded = JSON.stringify(context);
19958
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
19959
+ if (context.gaps.length)
19960
+ context.gaps.pop();
19961
+ else if (context.decisions.length)
19962
+ context.decisions.pop();
19963
+ else
19964
+ context.methods.pop();
19965
+ encoded = JSON.stringify(context);
19966
+ }
19967
+ if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
19968
+ context.objective = compactText(context.objective, 64) ?? "";
19969
+ context.nextAction = compactText(context.nextAction, 64);
19970
+ encoded = JSON.stringify(context);
19971
+ }
19972
+ return encoded;
19973
+ }
19974
+ var exportDigest = (bundle) => sha256({
19975
+ schemaVersion: bundle.schemaVersion,
19976
+ exportedAt: bundle.exportedAt,
19977
+ sourceWorkspaceId: bundle.sourceWorkspaceId,
19978
+ task: bundle.task
19979
+ });
19980
+ // packages/workit-core/src/core/external-action.ts
19981
+ var chainStepSchema = union([
19982
+ string2().min(1),
19983
+ object({ branch: string2().min(1) }).strict(),
19984
+ object({ pr: literal(true) }).strict()
19985
+ ]);
19986
+ var externalActionSchema = discriminatedUnion("operation", [
19987
+ object({
19988
+ operation: literal("git.branch_setup"),
19989
+ payload: object({
19990
+ action: _enum(["setup", "reapply_stash"]).optional(),
19991
+ sdd_dir: string2().optional(),
19992
+ target_branch: string2().optional(),
19993
+ stash: _enum(["yes", "no"]).optional(),
19994
+ cwd: string2().min(1).optional()
19995
+ }).strict()
19996
+ }).strict(),
19997
+ object({
19998
+ operation: literal("git.commit"),
19999
+ payload: object({
20000
+ message: string2().min(1).optional(),
20001
+ plan_steps: array(chainStepSchema).min(1).max(32).optional(),
20002
+ plan_branch: string2().min(1).optional(),
20003
+ cwd: string2().min(1).optional()
20004
+ }).strict()
20005
+ }).strict(),
20006
+ object({
20007
+ operation: literal("git.push"),
20008
+ payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
20009
+ }).strict(),
20010
+ object({
20011
+ operation: literal("hosting.pull_request"),
20012
+ payload: object({
20013
+ title: string2().min(1),
20014
+ body: string2().optional(),
20015
+ draft: boolean2().optional(),
20016
+ target_branch: string2().optional(),
20017
+ babysit: boolean2().optional(),
20018
+ cwd: string2().min(1).optional()
20019
+ }).strict()
20020
+ }).strict(),
20021
+ object({
20022
+ operation: literal("hosting.merge"),
20023
+ payload: object({
20024
+ target_branch: string2().min(1).optional(),
20025
+ source_branch: string2().min(1).optional(),
20026
+ cwd: string2().min(1).optional()
20027
+ }).strict()
20028
+ }).strict(),
20029
+ object({
20030
+ operation: literal("hosting.delete_branch"),
20031
+ payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
20032
+ }).strict(),
20033
+ object({
20034
+ operation: literal("youtrack.update"),
20035
+ payload: object({
20036
+ issueId: string2().min(1),
20037
+ markdown: string2().min(1),
20038
+ minutes: number2().positive().optional()
20039
+ }).strict()
20040
+ }).strict(),
20041
+ object({
20042
+ operation: literal("youtrack.time"),
20043
+ payload: object({
20044
+ issueId: string2().min(1),
20045
+ minutes: number2().positive(),
20046
+ text: string2().optional(),
20047
+ dateMs: number2().finite().optional()
20048
+ }).strict()
20049
+ }).strict(),
20050
+ object({
20051
+ operation: literal("youtrack.meeting"),
20052
+ payload: object({
20053
+ issueId: string2().min(1),
20054
+ minutes: number2().positive(),
20055
+ text: string2().min(1)
20056
+ }).strict()
20057
+ }).strict(),
20058
+ object({
20059
+ operation: literal("changelog.apply"),
20060
+ payload: object({
20061
+ entries: union([
20062
+ record(string2(), array(string2())),
20063
+ array(object({ category: string2().min(1), text: string2().min(1) }).strict())
20064
+ ]).optional(),
20065
+ path: string2().optional(),
20066
+ normalize_only: boolean2().optional()
20067
+ }).strict()
20068
+ }).strict(),
20069
+ object({
20070
+ operation: literal("context.read"),
20071
+ payload: object({
20072
+ kind: _enum([
20073
+ "git",
20074
+ "pr",
20075
+ "youtrack",
20076
+ "github_issue",
20077
+ "gitlab_issue",
20078
+ "changelog",
20079
+ "release",
20080
+ "affected"
20081
+ ]),
20082
+ range: string2().optional(),
20083
+ issueId: string2().optional(),
20084
+ issueUrl: string2().optional(),
20085
+ issueRef: string2().optional(),
20086
+ mode: string2().optional(),
20087
+ specPath: string2().optional(),
20088
+ planPath: string2().optional(),
20089
+ cwd: string2().min(1).optional()
20090
+ }).strict()
20091
+ }).strict()
20092
+ ]);
20093
+ // packages/workit-core/src/core/task-engine.ts
20094
+ import { realpathSync as realpathSync6 } from "node:fs";
20095
+
20096
+ // packages/workit-core/src/core/policy-resolver.ts
20097
+ var resolverInputSchema = object({
20098
+ intent: intentSchema,
20099
+ assessment: assessmentSchema,
20100
+ constraints: array(constraintSchema),
20101
+ prior: object({
20102
+ decisions: array(decisionSchema),
20103
+ findings: array(findingSchema),
20104
+ requirements: array(requirementSchema)
20105
+ }).strict(),
20106
+ preferences: object({ fast: boolean2().optional(), thorough: boolean2().optional() }).strict().optional()
20107
+ }).strict();
20108
+ var compareCodeUnits3 = (left, right) => {
20109
+ const length = Math.min(left.length, right.length);
20110
+ for (let index = 0;index < length; index += 1) {
20111
+ const difference = left.charCodeAt(index) - right.charCodeAt(index);
20112
+ if (difference !== 0)
20113
+ return difference;
20114
+ }
20115
+ return left.length - right.length;
19767
20116
  };
19768
- function selectMethods(policy, capabilities) {
19769
- return Object.entries(METHODS).flatMap(([id, definition]) => {
19770
- const requirements = policy.requirements.filter((requirement) => methodMatches(definition, requirement));
19771
- if (requirements.length === 0)
19772
- return [];
19773
- const capability = assuranceFor(id, definition, capabilities);
19774
- const reasons = [
19775
- ...new Set(requirements.map((requirement) => requirement.reason)),
19776
- ...capability.reasons
19777
- ];
19778
- if (capability.assurance === "unavailable")
19779
- reasons.unshift("required capability is unavailable");
19780
- return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
19781
- });
19782
- }
19783
-
19784
- // packages/workit-core/src/core/task-context.ts
19785
- function reconcileResume(view, observations = []) {
19786
- if (observations.length > 0)
19787
- return failure2("permission_denied", "worker observations require native host verification");
19788
- const candidate = captureCandidate(view.workspace.root, view.task.intent.data.scope, []);
19789
- if (!candidate.ok)
19790
- return candidate;
19791
- const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
19792
- const blockers = [...view.task.progress.blockers];
19793
- if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
19794
- blockers.push({
19795
- reason: "worker state requires reconciliation",
19796
- dependentAction: "resume",
19797
- refs: []
20117
+ var stableUnique = (values) => [...new Set(values)].toSorted(compareCodeUnits3);
20118
+ var stableList = (values) => [...values].toSorted((left, right) => compareCodeUnits3(canonicalJson(left), canonicalJson(right)));
20119
+ var normalizedScope = (scope) => ({
20120
+ description: scope.description,
20121
+ paths: stableUnique(scope.paths),
20122
+ exclusions: stableUnique(scope.exclusions)
20123
+ });
20124
+ var normalizedRefList = (values) => stableList(values);
20125
+ var normalizeAssessment = (assessment) => ({
20126
+ facts: stableList(assessment.facts.map((fact) => ({ ...fact, refs: normalizedRefList(fact.refs) }))),
20127
+ signals: Object.fromEntries(Object.entries(assessment.signals).map(([name, signal]) => [
20128
+ name,
20129
+ { ...signal, refs: normalizedRefList(signal.refs) }
20130
+ ])),
20131
+ consequences: stableList(assessment.consequences.map((consequence) => ({
20132
+ ...consequence,
20133
+ fact: { ...consequence.fact, refs: normalizedRefList(consequence.fact.refs) }
20134
+ }))),
20135
+ verification: stableList(assessment.verification.map((verification) => ({
20136
+ ...verification,
20137
+ scope: normalizedScope(verification.scope),
20138
+ availableChecks: normalizedRefList(verification.availableChecks),
20139
+ gaps: stableUnique(verification.gaps)
20140
+ })))
20141
+ });
20142
+ var normalizeConstraint = (constraint) => ({
20143
+ ...constraint,
20144
+ source: constraint.source,
20145
+ requires: stableList(constraint.requires.map((requirement) => ({
20146
+ ...requirement,
20147
+ scope: normalizedScope(requirement.scope),
20148
+ refs: normalizedRefList(requirement.refs)
20149
+ })))
20150
+ });
20151
+ var normalizeDecision = (decision) => ({
20152
+ ...decision,
20153
+ binding: {
20154
+ ...decision.binding,
20155
+ scope: normalizedScope(decision.binding.scope),
20156
+ contentRefs: normalizedRefList(decision.binding.contentRefs)
20157
+ },
20158
+ requirementIds: stableUnique(decision.requirementIds)
20159
+ });
20160
+ var normalizeIntent = (intent) => ({
20161
+ ...intent,
20162
+ scope: normalizedScope(intent.scope),
20163
+ authorityRefs: normalizedRefList(intent.authorityRefs)
20164
+ });
20165
+ var normalizePrior = (prior) => ({
20166
+ decisions: stableList(prior.decisions.map(normalizeDecision)),
20167
+ findings: stableList(prior.findings.map((finding) => ({
20168
+ ...finding,
20169
+ scope: normalizedScope(finding.scope),
20170
+ refs: normalizedRefList(finding.refs),
20171
+ resolution: finding.resolution ? {
20172
+ ...finding.resolution,
20173
+ evidenceIds: stableUnique(finding.resolution.evidenceIds),
20174
+ decisionIds: stableUnique(finding.resolution.decisionIds)
20175
+ } : null
20176
+ }))),
20177
+ requirements: stableList(prior.requirements.map((requirement) => ({
20178
+ ...requirement,
20179
+ scope: normalizedScope(requirement.scope)
20180
+ })))
20181
+ });
20182
+ var invalidFields = (error) => ({
20183
+ fields: error.issues.map((issue) => ({
20184
+ path: issue.code === "unrecognized_keys" ? issue.keys.join(".") : issue.path.join("."),
20185
+ reason: issue.message
20186
+ }))
20187
+ });
20188
+ function normalizeResolverInput(input) {
20189
+ const parsed = resolverInputSchema.safeParse(input);
20190
+ if (!parsed.success)
20191
+ return failure2("invalid_input", "resolver input is invalid", invalidFields(parsed.error));
20192
+ const value = parsed.data;
20193
+ const signals = value.assessment.signals;
20194
+ if (signals.behaviorChange.value === true && signals.mechanicalLowRisk.value === true)
20195
+ return failure2("invalid_input", "behavioral change cannot be wholly mechanical", {
20196
+ fields: [
20197
+ {
20198
+ path: "assessment.signals",
20199
+ reason: "contradictory behaviorChange and mechanicalLowRisk"
20200
+ }
20201
+ ]
19798
20202
  });
19799
- return success(null, null, {
19800
- candidate: candidate.data,
19801
- staleEvidenceIds,
19802
- workerUpdates: [],
19803
- blockers,
19804
- reassessmentRequired: view.task.policy?.policyVersion !== POLICY_VERSION
19805
- });
20203
+ const ids = value.constraints.map((constraint) => constraint.id);
20204
+ if (new Set(ids).size !== ids.length)
20205
+ return failure2("invalid_input", "constraint IDs must be unique", {
20206
+ fields: [{ path: "constraints", reason: "duplicate constraint id" }]
20207
+ });
20208
+ if (value.preferences?.fast && value.preferences.thorough)
20209
+ return failure2("invalid_input", "fast and thorough preferences contradict", {
20210
+ fields: [{ path: "preferences", reason: "fast and thorough cannot both be enabled" }]
20211
+ });
20212
+ const normalized = {
20213
+ intent: normalizeIntent(value.intent),
20214
+ assessment: normalizeAssessment(value.assessment),
20215
+ constraints: stableList(value.constraints.map(normalizeConstraint)),
20216
+ prior: normalizePrior(value.prior),
20217
+ ...value.preferences ? { preferences: { ...value.preferences } } : {},
20218
+ policyVersion: POLICY_VERSION
20219
+ };
20220
+ return success(null, null, normalized);
19806
20221
  }
19807
- var COMPACT_MAX_BYTES = 4096;
19808
- var COMPACT_MAX_ITEMS = 8;
19809
- var COMPACT_TEXT_BYTES = 128;
19810
- var sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
19811
- var sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
19812
- var privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
19813
- var compactText = (value, limit) => value === null ? null : value.replace(privateKey, "<redacted-private-key>").replace(sensitiveUrl, "<redacted-url>").replace(sensitiveValue, (match) => {
19814
- const separator = match.search(/[:=]/u);
19815
- if (separator >= 0)
19816
- return `${match.slice(0, separator)}=<redacted>`;
19817
- const space = match.search(/\s/u);
19818
- return `${match.slice(0, space)} <redacted>`;
19819
- }).slice(0, limit);
19820
- var compactBytes = (value) => new TextEncoder().encode(value).byteLength;
19821
- var compactTimestamp = (value) => `${value.slice(0, 19)}.${value.slice(20, -1).padEnd(9, "0")}`;
19822
- var compactDecisionChoice = (entry) => {
19823
- const stated = entry.data.binding.statedChoice?.text;
19824
- const approved = entry.data.response === "approved" ? entry.data.binding.approvedContent : "";
19825
- const source = stated?.trim() || approved.trim() || null;
19826
- return compactText(source, COMPACT_TEXT_BYTES);
19827
- };
19828
- var compactReference = (ref) => {
19829
- if (ref.kind === "file")
19830
- return {
19831
- kind: "file",
19832
- path: compactText(ref.path, COMPACT_TEXT_BYTES) ?? ""
19833
- };
19834
- if (ref.kind === "record")
19835
- return {
19836
- kind: "record",
19837
- collection: ref.collection,
19838
- id: ref.id
19839
- };
19840
- return { kind: ref.kind };
19841
- };
19842
- function compactTaskContext(view) {
19843
- const decisions = view.task.decisions.slice().toSorted((left, right) => {
19844
- const leftAt = compactTimestamp(left.recordedAt);
19845
- const rightAt = compactTimestamp(right.recordedAt);
19846
- if (leftAt !== rightAt)
19847
- return leftAt > rightAt ? -1 : 1;
19848
- return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
19849
- }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
19850
- id: entry.id,
19851
- purpose: entry.data.purpose,
19852
- status: entry.data.response,
19853
- digest: entry.data.digest,
19854
- choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
19855
- references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
19856
- }));
19857
- const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
19858
- id: method.id,
19859
- assurance: method.assurance,
19860
- reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
19861
- })) : [];
19862
- const gaps = Array.from(new Set([
19863
- ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
19864
- ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
19865
- ...view.task.progress.blockers.map((entry) => entry.reason)
19866
- ])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
19867
- const context = {
19868
- objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
19869
- status: view.task.status,
19870
- decisions,
19871
- methods,
19872
- gaps,
19873
- nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
20222
+ var requirement = (spec) => ({
20223
+ ...spec,
20224
+ id: requirementId({
20225
+ ruleId: spec.ruleId,
20226
+ scope: spec.scope,
20227
+ satisfaction: spec.satisfaction,
20228
+ before: spec.before,
20229
+ dependentAction: spec.dependentAction
20230
+ })
20231
+ });
20232
+ var signalReason = (input, name) => input.assessment.signals[name].reason;
20233
+ function resolveRequirements(input) {
20234
+ const { signals } = input.assessment;
20235
+ const scope = input.intent.scope;
20236
+ const requirements = [];
20237
+ const hasUnknownSignal = Object.values(signals).some((signal) => signal.value === "unknown");
20238
+ const hasUnknownFact = [
20239
+ ...input.assessment.facts,
20240
+ ...input.assessment.consequences.map(({ fact }) => fact)
20241
+ ].some((fact) => fact.basis === "unknown");
20242
+ if (hasUnknownSignal || hasUnknownFact)
20243
+ requirements.push(requirement({
20244
+ ruleId: "consequential-unknown",
20245
+ dimension: "investigation",
20246
+ scope,
20247
+ reason: `Resolve consequential uncertainty before the dependent action: ${[
20248
+ ...Object.entries(signals).filter(([, signal]) => signal.value === "unknown").map(([name]) => name),
20249
+ ...hasUnknownFact ? ["unknown fact"] : []
20250
+ ].join(", ")}`,
20251
+ satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
20252
+ before: "dependent_action",
20253
+ dependentAction: null,
20254
+ acceptanceAllowed: false
20255
+ }));
20256
+ if (signals.productChoiceOpen.value === true)
20257
+ requirements.push(requirement({
20258
+ ruleId: "product-decision",
20259
+ dimension: "decisions",
20260
+ scope,
20261
+ reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
20262
+ satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
20263
+ before: "dependent_action",
20264
+ dependentAction: null,
20265
+ acceptanceAllowed: false
20266
+ }));
20267
+ const independentBehaviorReview = input.preferences?.thorough === true || input.assessment.consequences.some((consequence) => ["security", "data", "public_contract", "operations"].includes(consequence.area));
20268
+ if (signals.behaviorChange.value === true)
20269
+ requirements.push(requirement({
20270
+ ruleId: "behavioral-verification",
20271
+ dimension: "testing",
20272
+ scope,
20273
+ reason: `Behavior, side effects, permissions, or data handling may change: ${signalReason(input, "behaviorChange")}`,
20274
+ satisfaction: "A useful behavioral check passes against the affected behavior and current candidate.",
20275
+ before: "close",
20276
+ dependentAction: null,
20277
+ acceptanceAllowed: false
20278
+ }), requirement({
20279
+ ruleId: independentBehaviorReview ? "fresh-context-review" : "self-review",
20280
+ dimension: "review",
20281
+ scope,
20282
+ reason: independentBehaviorReview ? "Consequential behavior or the thorough preference requires fresh-context review." : "Bounded behavior changes use self-review unless consequential risks or trusted rules require more.",
20283
+ satisfaction: independentBehaviorReview ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
20284
+ before: "close",
20285
+ dependentAction: null,
20286
+ acceptanceAllowed: !independentBehaviorReview
20287
+ }));
20288
+ else if (signals.mechanicalLowRisk.value === true)
20289
+ requirements.push(requirement({
20290
+ ruleId: "mechanical-existing-checks",
20291
+ dimension: "verification",
20292
+ scope,
20293
+ reason: `The change is demonstrably mechanical and low-risk: ${signalReason(input, "mechanicalLowRisk")}`,
20294
+ satisfaction: "Run the relevant existing checks for affected consumers; do not add tautological tests.",
20295
+ before: "close",
20296
+ dependentAction: null,
20297
+ acceptanceAllowed: true
20298
+ }), requirement({
20299
+ ruleId: input.preferences?.thorough ? "fresh-context-review" : "self-review",
20300
+ dimension: "review",
20301
+ scope,
20302
+ reason: input.preferences?.thorough ? "The thorough preference upgrades mechanical self-review to fresh-context review." : "Mechanical low-risk work uses self-review rather than mandatory fresh-context review.",
20303
+ satisfaction: input.preferences?.thorough ? "A separate review context examines the current candidate and records its result." : "The lead reviews the resulting diff and records the relevant scope and checks.",
20304
+ before: "close",
20305
+ dependentAction: null,
20306
+ acceptanceAllowed: !input.preferences?.thorough
20307
+ }));
20308
+ if (signals.behaviorChange.value === true || signals.mechanicalLowRisk.value === true)
20309
+ requirements.push(requirement({
20310
+ ruleId: "pre-pr-cleanup",
20311
+ dimension: "verification",
20312
+ scope,
20313
+ reason: "Implementation output must be deslopped before it is delivered.",
20314
+ satisfaction: "Run the workit-deslop pass and record passing check evidence, or record an approved limitation decision waiving cleanup.",
20315
+ before: "dependent_action",
20316
+ dependentAction: "hosting.pull_request",
20317
+ acceptanceAllowed: true
20318
+ }));
20319
+ if (signals.durableAgreementNeeded.value === true)
20320
+ requirements.push(requirement({
20321
+ ruleId: "durable-spec",
20322
+ dimension: "artifacts",
20323
+ scope,
20324
+ reason: `The work needs a durable agreement about intended behavior: ${signalReason(input, "durableAgreementNeeded")}`,
20325
+ satisfaction: "Record the agreed behavior in a durable specification before implementation writes.",
20326
+ before: "write",
20327
+ dependentAction: null,
20328
+ acceptanceAllowed: true
20329
+ }));
20330
+ if (signals.coordinationPlanNeeded.value === true)
20331
+ requirements.push(requirement({
20332
+ ruleId: "coordination-plan",
20333
+ dimension: "continuity",
20334
+ scope,
20335
+ reason: `Dependencies, sequencing, or resumption need durable coordination: ${signalReason(input, "coordinationPlanNeeded")}`,
20336
+ satisfaction: "Record useful sequencing, dependencies, and the next action in a compact plan.",
20337
+ before: "dependent_action",
20338
+ dependentAction: null,
20339
+ acceptanceAllowed: true
20340
+ }));
20341
+ if (signals.helperUseful.value === true && !input.preferences?.fast)
20342
+ requirements.push(requirement({
20343
+ ruleId: "helper-usefulness",
20344
+ dimension: "delegation",
20345
+ scope,
20346
+ reason: `Independent judgment or investigation justifies a scoped helper: ${signalReason(input, "helperUseful")}`,
20347
+ satisfaction: "Use a bounded helper only for the independent objective and reconcile its result.",
20348
+ before: "dependent_action",
20349
+ dependentAction: null,
20350
+ acceptanceAllowed: true
20351
+ }));
20352
+ for (const constraint of input.constraints)
20353
+ for (const obligation of constraint.requires) {
20354
+ const dimension = obligation.method ?? (obligation.kind === "check" ? "verification" : obligation.kind === "decision" ? "decisions" : "challenge");
20355
+ requirements.push(requirement({
20356
+ ruleId: `project-constraint:${constraint.id}:${obligation.kind}:${sha256({
20357
+ kind: obligation.kind,
20358
+ scope: normalizedScope(obligation.scope),
20359
+ refs: normalizedRefList(obligation.refs),
20360
+ method: obligation.method,
20361
+ before: obligation.before,
20362
+ dependentAction: obligation.dependentAction
20363
+ })}`,
20364
+ dimension,
20365
+ scope: obligation.scope,
20366
+ reason: `${constraint.kind} constraint requires ${obligation.kind}: ${constraint.statement}`,
20367
+ satisfaction: `Satisfy the constraint obligation using its referenced ${obligation.kind}.`,
20368
+ before: obligation.before,
20369
+ dependentAction: obligation.dependentAction,
20370
+ acceptanceAllowed: constraint.acceptanceAllowed && obligation.kind !== "decision"
20371
+ }));
20372
+ }
20373
+ const seen = new Set;
20374
+ return requirements.filter((item) => {
20375
+ if (seen.has(item.id))
20376
+ return false;
20377
+ seen.add(item.id);
20378
+ return true;
20379
+ });
20380
+ }
20381
+ function resolvePolicy(input) {
20382
+ const normalized = normalizeResolverInput(input);
20383
+ if (!normalized.ok)
20384
+ return normalized;
20385
+ const requirements = resolveRequirements(normalized.data);
20386
+ const policy = {
20387
+ policyVersion: POLICY_VERSION,
20388
+ inputDigest: sha256(canonicalJson(normalized.data)),
20389
+ requirements
19874
20390
  };
19875
- let encoded = JSON.stringify(context);
19876
- while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
19877
- if (context.gaps.length)
19878
- context.gaps.pop();
19879
- else if (context.decisions.length)
19880
- context.decisions.pop();
19881
- else
19882
- context.methods.pop();
19883
- encoded = JSON.stringify(context);
19884
- }
19885
- if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
19886
- context.objective = compactText(context.objective, 64) ?? "";
19887
- context.nextAction = compactText(context.nextAction, 64);
19888
- encoded = JSON.stringify(context);
19889
- }
19890
- return encoded;
20391
+ const parsed = policySchema.safeParse(policy);
20392
+ if (!parsed.success)
20393
+ return failure2("invalid_input", "resolved policy is invalid", invalidFields(parsed.error));
20394
+ return success(null, null, parsed.data);
20395
+ }
20396
+ function diffPolicy(previous, next, reason, now) {
20397
+ if (!policySchema.safeParse(previous).success && previous !== null)
20398
+ throw new TypeError("previous policy is invalid");
20399
+ if (!policySchema.safeParse(next).success)
20400
+ throw new TypeError("next policy is invalid");
20401
+ const oldIds = new Set(previous?.requirements.map((item) => item.id) ?? []);
20402
+ const newIds = new Set(next.requirements.map((item) => item.id));
20403
+ const added = [...newIds].filter((id) => !oldIds.has(id)).toSorted(compareCodeUnits3);
20404
+ const retired = [...oldIds].filter((id) => !newIds.has(id)).toSorted(compareCodeUnits3);
20405
+ if (added.length === 0 && retired.length === 0)
20406
+ return null;
20407
+ const normalizedReason = reason.trim();
20408
+ const changeReason = retired.length ? `${normalizedReason || "policy reassessed"} (retired: ${retired.join(",")})` : normalizedReason || "policy reassessed";
20409
+ const change = {
20410
+ recordedAt: now,
20411
+ fromInputDigest: previous?.inputDigest ?? null,
20412
+ toInputDigest: next.inputDigest,
20413
+ added,
20414
+ retired,
20415
+ reason: changeReason
20416
+ };
20417
+ const parsed = policyChangeSchema.safeParse(change);
20418
+ if (!parsed.success)
20419
+ throw new TypeError("policy change is invalid");
20420
+ return parsed.data;
19891
20421
  }
19892
- var exportDigest = (bundle) => sha256({
19893
- schemaVersion: bundle.schemaVersion,
19894
- exportedAt: bundle.exportedAt,
19895
- sourceWorkspaceId: bundle.sourceWorkspaceId,
19896
- task: bundle.task
19897
- });
19898
20422
 
19899
20423
  // packages/workit-core/src/core/task-engine.ts
19900
20424
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
@@ -20067,6 +20591,29 @@ var applyWorkerLifecycle = (input) => {
20067
20591
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
20068
20592
  };
20069
20593
  var refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || bindingCovers(scope, { description: "", paths: [ref.path], exclusions: [] }));
20594
+ var REVISION_RETRY_ATTEMPTS = 8;
20595
+ var retryPause = new Int32Array(new SharedArrayBuffer(4));
20596
+ var filledRevisions = (request) => {
20597
+ const value = typeof request === "object" && request !== null ? request : {};
20598
+ return {
20599
+ task: value.expectedRevision === undefined,
20600
+ workspace: value.expectedWorkspaceRevision === undefined
20601
+ };
20602
+ };
20603
+ var filledConflict = (result, filled) => !result.ok && result.code === "revision_conflict" && (("actualRevision" in result.details) && filled.task || ("actualWorkspaceRevision" in result.details) && filled.workspace);
20604
+ var retryFilledRevisions = (filled, run) => {
20605
+ if (!filled.task && !filled.workspace)
20606
+ return run();
20607
+ let result = run();
20608
+ const deadline = Date.now() + defaultLockTimeout();
20609
+ for (let attempt = 1;filledConflict(result, filled); attempt += 1) {
20610
+ if (attempt >= REVISION_RETRY_ATTEMPTS || Date.now() >= deadline)
20611
+ return failure2("busy", "records kept changing under concurrent writers; retry the call");
20612
+ Atomics.wait(retryPause, 0, 0, Math.floor(Math.random() * 4 * attempt) + 1);
20613
+ result = run();
20614
+ }
20615
+ return result;
20616
+ };
20070
20617
  var trustedNow = (context) => typeof context.now === "function" ? context.now() : context.now;
20071
20618
  var mapRef = (ref, ids) => {
20072
20619
  if (ref.kind !== "record")
@@ -20314,6 +20861,9 @@ class WorkitCore {
20314
20861
  if (input.expectedWorkspaceRevision === undefined)
20315
20862
  input.expectedWorkspaceRevision = workspace ? workspace.revision : null;
20316
20863
  }
20864
+ retryOmittedRevisions(request, run) {
20865
+ return retryFilledRevisions(filledRevisions(request), run);
20866
+ }
20317
20867
  helperEntry(task, requireSession = false, requireActive = false) {
20318
20868
  const workerId = this.context.workerId ?? null;
20319
20869
  if (workerId === null)
@@ -20345,6 +20895,9 @@ class WorkitCore {
20345
20895
  return null;
20346
20896
  }
20347
20897
  state(request) {
20898
+ return request?.action === "import" ? this.retryOmittedRevisions(request, () => this.stateOnce(request)) : this.stateOnce(request);
20899
+ }
20900
+ stateOnce(request) {
20348
20901
  const root = this.contextRootError();
20349
20902
  if (!root.ok)
20350
20903
  return root;
@@ -20446,6 +20999,9 @@ class WorkitCore {
20446
20999
  });
20447
21000
  }
20448
21001
  task(request) {
21002
+ return this.retryOmittedRevisions(request, () => this.taskOnce(request));
21003
+ }
21004
+ taskOnce(request) {
20449
21005
  const root = this.contextRootError();
20450
21006
  if (!root.ok)
20451
21007
  return root;
@@ -20545,6 +21101,9 @@ class WorkitCore {
20545
21101
  }
20546
21102
  }
20547
21103
  policy(request) {
21104
+ return this.retryOmittedRevisions(request, () => this.policyOnce(request));
21105
+ }
21106
+ policyOnce(request) {
20548
21107
  const root = this.contextRootError();
20549
21108
  if (!root.ok)
20550
21109
  return root;
@@ -20591,6 +21150,9 @@ class WorkitCore {
20591
21150
  return success(changed.data.revision, null, changed.data.policy);
20592
21151
  }
20593
21152
  evidence(request) {
21153
+ return this.retryOmittedRevisions(request, () => this.evidenceOnce(request));
21154
+ }
21155
+ evidenceOnce(request) {
20594
21156
  const root = this.contextRootError();
20595
21157
  if (!root.ok)
20596
21158
  return root;
@@ -20675,6 +21237,23 @@ class WorkitCore {
20675
21237
  observeStandingDecision(request) {
20676
21238
  return this.recordDecision(request, undefined, true, true);
20677
21239
  }
21240
+ commitTask(taskId, expected, filled, recheck, update) {
21241
+ let revision = expected;
21242
+ let first = true;
21243
+ return retryFilledRevisions({ task: filled, workspace: false }, () => {
21244
+ if (!first) {
21245
+ const fresh = this.store.readTask(taskId);
21246
+ if (!fresh.ok)
21247
+ return fresh;
21248
+ const checked = recheck(fresh.data);
21249
+ if (!checked.ok)
21250
+ return checked;
21251
+ revision = fresh.data.revision;
21252
+ }
21253
+ first = false;
21254
+ return this.store.mutateTask(taskId, revision, update, trustedNow(this.context));
21255
+ });
21256
+ }
20678
21257
  recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
20679
21258
  const root = this.contextRootError();
20680
21259
  if (!root.ok)
@@ -20699,6 +21278,7 @@ class WorkitCore {
20699
21278
  return task;
20700
21279
  if ((this.context.workerId ?? null) !== null)
20701
21280
  return failure2("permission_denied", "helpers cannot record or revoke decisions");
21281
+ const filled = input.expectedRevision === undefined;
20702
21282
  this.fillRevisions(input, task.data);
20703
21283
  if (input.action === "record") {
20704
21284
  if (task.data.status === "closed")
@@ -20747,7 +21327,20 @@ class WorkitCore {
20747
21327
  const nativeProvenance = native?.ok ? retireNativeAuthority(native.data) : null;
20748
21328
  if (native?.ok && !nativeProvenance)
20749
21329
  return failure2("permission_denied", "native decision authority was retired");
20750
- const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
21330
+ let standingProvenance = standingApproval?.ok === true ? standingApproval.data : null;
21331
+ const recheck = (fresh) => {
21332
+ if (fresh.status === "closed")
21333
+ return failure2("invalid_transition", "closed task cannot record a decision");
21334
+ if (!standingProvenance)
21335
+ return success(null, null, null);
21336
+ const again = verifyStandingApproval(this.store.root, fresh, this.context.caller, input.binding);
21337
+ if (again.ok)
21338
+ standingProvenance = again.data;
21339
+ return again;
21340
+ };
21341
+ const changed = this.commitTask(task.data.id, input.expectedRevision, filled, recheck, (current, mutation) => {
21342
+ if (current.status === "closed")
21343
+ return failure2("invalid_transition", "closed task cannot record a decision");
20751
21344
  const latestContent = verifyDecisionContent(this.store, input.binding);
20752
21345
  if (!latestContent.ok)
20753
21346
  return latestContent;
@@ -20759,14 +21352,14 @@ class WorkitCore {
20759
21352
  const entry = {
20760
21353
  id: newId(),
20761
21354
  recordedAt: mutation.now,
20762
- provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
21355
+ provenance: standingProvenance ?? nativeProvenance ?? provenance(this.context, "agent_reported"),
20763
21356
  data
20764
21357
  };
20765
21358
  return success(mutation.revision, null, {
20766
21359
  ...current,
20767
21360
  decisions: [...current.decisions, entry]
20768
21361
  });
20769
- }, trustedNow(this.context));
21362
+ });
20770
21363
  if (!changed.ok)
20771
21364
  return changed;
20772
21365
  return success(changed.data.revision, null, changed.data.decisions.at(-1));
@@ -20780,7 +21373,7 @@ class WorkitCore {
20780
21373
  return failure2("invalid_transition", "decision is already revoked");
20781
21374
  if (decision.data.consumption)
20782
21375
  return failure2("permission_denied", "consumed decision cannot be revoked");
20783
- const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
21376
+ const changed = this.commitTask(task.data.id, input.expectedRevision, filled, () => success(null, null, null), (current, mutation) => {
20784
21377
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
20785
21378
  if (!entry)
20786
21379
  return failure2("not_found", "decision not found");
@@ -20797,13 +21390,16 @@ class WorkitCore {
20797
21390
  ...current,
20798
21391
  decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? updated : candidate)
20799
21392
  });
20800
- }, trustedNow(this.context));
21393
+ });
20801
21394
  if (!changed.ok)
20802
21395
  return changed;
20803
21396
  const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
20804
21397
  return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "decision disappeared");
20805
21398
  }
20806
21399
  finding(request) {
21400
+ return this.retryOmittedRevisions(request, () => this.findingOnce(request));
21401
+ }
21402
+ findingOnce(request) {
20807
21403
  const root = this.contextRootError();
20808
21404
  if (!root.ok)
20809
21405
  return root;
@@ -20927,6 +21523,9 @@ class WorkitCore {
20927
21523
  return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "finding disappeared");
20928
21524
  }
20929
21525
  worker(request) {
21526
+ return this.retryOmittedRevisions(request, () => this.workerOnce(request));
21527
+ }
21528
+ workerOnce(request) {
20930
21529
  const root = this.contextRootError();
20931
21530
  if (!root.ok)
20932
21531
  return root;
@@ -21274,6 +21873,9 @@ class WorkitCore {
21274
21873
  return applied;
21275
21874
  }
21276
21875
  writer(request) {
21876
+ return this.retryOmittedRevisions(request, () => this.writerOnce(request));
21877
+ }
21878
+ writerOnce(request) {
21277
21879
  const root = this.contextRootError();
21278
21880
  if (!root.ok)
21279
21881
  return root;
@@ -21876,122 +22478,6 @@ class WorkitCore {
21876
22478
  return this.summary(changed.data.task);
21877
22479
  }
21878
22480
  }
21879
-
21880
- // packages/workit-core/src/core/session-context.ts
21881
- var cache = new Map;
21882
- // packages/workit-core/src/core/external-action.ts
21883
- var chainStepSchema = union([
21884
- string2().min(1),
21885
- object({ branch: string2().min(1) }).strict(),
21886
- object({ pr: literal(true) }).strict()
21887
- ]);
21888
- var externalActionSchema = discriminatedUnion("operation", [
21889
- object({
21890
- operation: literal("git.branch_setup"),
21891
- payload: object({
21892
- action: _enum(["setup", "reapply_stash"]).optional(),
21893
- sdd_dir: string2().optional(),
21894
- target_branch: string2().optional(),
21895
- stash: _enum(["yes", "no"]).optional(),
21896
- cwd: string2().min(1).optional()
21897
- }).strict()
21898
- }).strict(),
21899
- object({
21900
- operation: literal("git.commit"),
21901
- payload: object({
21902
- message: string2().min(1).optional(),
21903
- plan_steps: array(chainStepSchema).min(1).max(32).optional(),
21904
- plan_branch: string2().min(1).optional(),
21905
- cwd: string2().min(1).optional()
21906
- }).strict()
21907
- }).strict(),
21908
- object({
21909
- operation: literal("git.push"),
21910
- payload: object({ branch: string2().min(1).optional(), cwd: string2().min(1).optional() }).strict()
21911
- }).strict(),
21912
- object({
21913
- operation: literal("hosting.pull_request"),
21914
- payload: object({
21915
- title: string2().min(1),
21916
- body: string2().optional(),
21917
- draft: boolean2().optional(),
21918
- target_branch: string2().optional(),
21919
- babysit: boolean2().optional(),
21920
- cwd: string2().min(1).optional()
21921
- }).strict()
21922
- }).strict(),
21923
- object({
21924
- operation: literal("hosting.merge"),
21925
- payload: object({
21926
- target_branch: string2().min(1).optional(),
21927
- source_branch: string2().min(1).optional(),
21928
- cwd: string2().min(1).optional()
21929
- }).strict()
21930
- }).strict(),
21931
- object({
21932
- operation: literal("hosting.delete_branch"),
21933
- payload: object({ branch: string2().min(1), cwd: string2().min(1).optional() }).strict()
21934
- }).strict(),
21935
- object({
21936
- operation: literal("youtrack.update"),
21937
- payload: object({
21938
- issueId: string2().min(1),
21939
- markdown: string2().min(1),
21940
- minutes: number2().positive().optional()
21941
- }).strict()
21942
- }).strict(),
21943
- object({
21944
- operation: literal("youtrack.time"),
21945
- payload: object({
21946
- issueId: string2().min(1),
21947
- minutes: number2().positive(),
21948
- text: string2().optional(),
21949
- dateMs: number2().finite().optional()
21950
- }).strict()
21951
- }).strict(),
21952
- object({
21953
- operation: literal("youtrack.meeting"),
21954
- payload: object({
21955
- issueId: string2().min(1),
21956
- minutes: number2().positive(),
21957
- text: string2().min(1)
21958
- }).strict()
21959
- }).strict(),
21960
- object({
21961
- operation: literal("changelog.apply"),
21962
- payload: object({
21963
- entries: union([
21964
- record(string2(), array(string2())),
21965
- array(object({ category: string2().min(1), text: string2().min(1) }).strict())
21966
- ]).optional(),
21967
- path: string2().optional(),
21968
- normalize_only: boolean2().optional()
21969
- }).strict()
21970
- }).strict(),
21971
- object({
21972
- operation: literal("context.read"),
21973
- payload: object({
21974
- kind: _enum([
21975
- "git",
21976
- "pr",
21977
- "youtrack",
21978
- "github_issue",
21979
- "gitlab_issue",
21980
- "changelog",
21981
- "release",
21982
- "affected"
21983
- ]),
21984
- range: string2().optional(),
21985
- issueId: string2().optional(),
21986
- issueUrl: string2().optional(),
21987
- issueRef: string2().optional(),
21988
- mode: string2().optional(),
21989
- specPath: string2().optional(),
21990
- planPath: string2().optional(),
21991
- cwd: string2().min(1).optional()
21992
- }).strict()
21993
- }).strict()
21994
- ]);
21995
22481
  // packages/workit-core/src/core.ts
21996
22482
  var revision2 = /^[A-Za-z0-9@][A-Za-z0-9@._/~^{}-]*$/;
21997
22483
  function gitRevisionParts(value) {
@@ -22035,7 +22521,7 @@ function resolveInside(root, candidate) {
22035
22521
  }
22036
22522
  function run2(root, executable, args, env = {}) {
22037
22523
  const cwd = realpathSync7(root);
22038
- const result = spawnSync3(executable, args, {
22524
+ const result = spawnSync4(executable, args, {
22039
22525
  cwd,
22040
22526
  encoding: "utf8",
22041
22527
  env: { ...process.env, ...env }
@@ -24495,9 +24981,9 @@ var applyPatterns = (value) => {
24495
24981
  var redactSecrets = (value) => applyPatterns(value);
24496
24982
 
24497
24983
  // packages/workit-core/src/core/pr-create.ts
24498
- import fs8 from "node:fs";
24984
+ import fs9 from "node:fs";
24499
24985
  import path12 from "node:path";
24500
- import { spawnSync as spawnSync4 } from "node:child_process";
24986
+ import { spawnSync as spawnSync5 } from "node:child_process";
24501
24987
  function parseGhIssue(value) {
24502
24988
  const m = /issues\/(\d+)/.exec(value);
24503
24989
  return m ? m[1] : String(value).trim().replace(/^#/, "");
@@ -24510,7 +24996,7 @@ function whichOnPath(tool) {
24510
24996
  for (const name of names) {
24511
24997
  const candidate = path12.join(dir, name);
24512
24998
  try {
24513
- fs8.accessSync(candidate, fs8.constants.X_OK);
24999
+ fs9.accessSync(candidate, fs9.constants.X_OK);
24514
25000
  return candidate;
24515
25001
  } catch {}
24516
25002
  }
@@ -24519,7 +25005,7 @@ function whichOnPath(tool) {
24519
25005
  }
24520
25006
  var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
24521
25007
  var pushRemote = (cwd) => {
24522
- const result = spawnSync4("git", ["remote", "get-url", "--push", "--all", "origin"], {
25008
+ const result = spawnSync5("git", ["remote", "get-url", "--push", "--all", "origin"], {
24523
25009
  cwd,
24524
25010
  encoding: "utf8"
24525
25011
  });
@@ -24530,7 +25016,7 @@ var pushRemote = (cwd) => {
24530
25016
  };
24531
25017
 
24532
25018
  // packages/workit-core/src/core/repo-context.ts
24533
- import { spawnSync as spawnSync5 } from "node:child_process";
25019
+ import { spawnSync as spawnSync6 } from "node:child_process";
24534
25020
  import { existsSync as existsSync6, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "node:fs";
24535
25021
  import path13 from "node:path";
24536
25022
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
@@ -24559,7 +25045,7 @@ var invalidRange = (cwd) => ({
24559
25045
  cwd
24560
25046
  });
24561
25047
  var runGit = (cwd, args) => {
24562
- const result = spawnSync5("git", args, { cwd, encoding: "utf8" });
25048
+ const result = spawnSync6("git", args, { cwd, encoding: "utf8" });
24563
25049
  return {
24564
25050
  stdout: (result.stdout ?? "").trimEnd(),
24565
25051
  stderr: (result.stderr ?? "").trimEnd(),
@@ -24732,7 +25218,7 @@ var documentationFiles = (cwd) => {
24732
25218
  walk(cwd, 1);
24733
25219
  return matches.toSorted().slice(0, 200);
24734
25220
  };
24735
- var readTrimmed = (file, maxLines) => {
25221
+ var readTrimmed2 = (file, maxLines) => {
24736
25222
  if (!existsSync6(file))
24737
25223
  return "";
24738
25224
  const lines = readFileSync7(file, "utf8").split(`
@@ -24799,7 +25285,7 @@ ${status.stderr}` : "");
24799
25285
  stdout += `template_path: ${template}
24800
25286
 
24801
25287
  `;
24802
- stdout += readTrimmed(path13.join(cwd, template), 220);
25288
+ stdout += readTrimmed2(path13.join(cwd, template), 220);
24803
25289
  } else {
24804
25290
  stdout += `template_path: none
24805
25291
 
@@ -24871,7 +25357,7 @@ function changelogContext(root, range) {
24871
25357
  stdout += printSection("Existing CHANGELOG.md");
24872
25358
  const changelogPath = path13.join(cwd, "CHANGELOG.md");
24873
25359
  if (existsSync6(changelogPath)) {
24874
- stdout += readTrimmed(changelogPath, 260) + `
25360
+ stdout += readTrimmed2(changelogPath, 260) + `
24875
25361
  `;
24876
25362
  } else {
24877
25363
  stdout += `CHANGELOG.md not found.
@@ -24913,7 +25399,7 @@ function docsRefreshContext(root, range) {
24913
25399
  stdout += printSection("README Preview");
24914
25400
  const readme = path13.join(cwd, "README.md");
24915
25401
  if (existsSync6(readme)) {
24916
- stdout += readTrimmed(readme, 220) + `
25402
+ stdout += readTrimmed2(readme, 220) + `
24917
25403
  `;
24918
25404
  } else {
24919
25405
  stdout += `README.md not found.
@@ -25000,7 +25486,7 @@ function releaseNotesContext(root, rangeOrTag) {
25000
25486
  }
25001
25487
 
25002
25488
  // packages/workit-core/src/core/tracker-issues.ts
25003
- import { spawnSync as spawnSync6 } from "node:child_process";
25489
+ import { spawnSync as spawnSync7 } from "node:child_process";
25004
25490
  var repoPathFromRemote = (remote) => {
25005
25491
  let rest = (remote || "").trim().replace(/\/+$/, "");
25006
25492
  if (!rest)
@@ -25021,7 +25507,7 @@ var defaultCli = (bin, available) => {
25021
25507
  if (!available)
25022
25508
  return null;
25023
25509
  return (args, root) => {
25024
- const out = spawnSync6(bin, args, {
25510
+ const out = spawnSync7(bin, args, {
25025
25511
  cwd: root,
25026
25512
  encoding: "utf8",
25027
25513
  env: { ...process.env, PATH: process.env.PATH ?? "" }
@@ -25099,7 +25585,7 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
25099
25585
  };
25100
25586
 
25101
25587
  // packages/workit-core/src/core/youtrack.ts
25102
- import fs9 from "node:fs";
25588
+ import fs10 from "node:fs";
25103
25589
  import path14 from "node:path";
25104
25590
 
25105
25591
  // packages/workit-core/src/core/templates.ts
@@ -25118,17 +25604,17 @@ var youTrackReadOnlyConfigPath = () => process.env.WORKFLOW_YOUTRACK_CONFIG ?? p
25118
25604
  var youTrackTokenModeOk = (p) => {
25119
25605
  if (process.platform === "win32")
25120
25606
  return true;
25121
- const mode = fs9.statSync(p).mode & 511;
25607
+ const mode = fs10.statSync(p).mode & 511;
25122
25608
  return mode === 384;
25123
25609
  };
25124
25610
  function readYouTrackConfig(required) {
25125
25611
  const cfgPath = youTrackConfigPath();
25126
- if (!fs9.existsSync(cfgPath)) {
25612
+ if (!fs10.existsSync(cfgPath)) {
25127
25613
  return required ? { error: "ERROR: missing youtrack.json", path: cfgPath } : { config: {}, path: cfgPath };
25128
25614
  }
25129
25615
  let parsed;
25130
25616
  try {
25131
- parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
25617
+ parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
25132
25618
  } catch {
25133
25619
  return { error: `${cfgPath} is not valid JSON`, path: cfgPath };
25134
25620
  }
@@ -25139,11 +25625,11 @@ function readYouTrackConfig(required) {
25139
25625
  }
25140
25626
  var readYouTrackContextConfig = () => {
25141
25627
  const cfgPath = youTrackReadOnlyConfigPath();
25142
- if (!fs9.existsSync(cfgPath))
25628
+ if (!fs10.existsSync(cfgPath))
25143
25629
  return { data: {} };
25144
25630
  let parsed;
25145
25631
  try {
25146
- parsed = JSON.parse(fs9.readFileSync(cfgPath, "utf8"));
25632
+ parsed = JSON.parse(fs10.readFileSync(cfgPath, "utf8"));
25147
25633
  } catch {
25148
25634
  return { error: "YouTrack configuration is unavailable" };
25149
25635
  }
@@ -25183,11 +25669,11 @@ var youTrackToken = () => {
25183
25669
  return loaded;
25184
25670
  const tokenFile = String(loaded.config.tokenFile ?? "");
25185
25671
  const tokenPath = tokenFile ? path14.isAbsolute(tokenFile) ? path14.resolve(tokenFile) : path14.resolve(process.cwd(), tokenFile) : "";
25186
- if (!tokenPath || !fs9.existsSync(tokenPath))
25672
+ if (!tokenPath || !fs10.existsSync(tokenPath))
25187
25673
  return { error: "missing youtrack.token" };
25188
25674
  if (!youTrackTokenModeOk(tokenPath))
25189
25675
  return { error: "youtrack.token mode must be 0600" };
25190
- const token = fs9.readFileSync(tokenPath, "utf8").trim();
25676
+ const token = fs10.readFileSync(tokenPath, "utf8").trim();
25191
25677
  if (!token)
25192
25678
  return { error: "empty token file" };
25193
25679
  if (token === TOKEN_PLACEHOLDER || token.startsWith(TOKEN_PLACEHOLDER)) {
@@ -25252,9 +25738,9 @@ function resolveYouTrackFromPaths(spec_path, plan_path, workspace_root) {
25252
25738
  } catch {
25253
25739
  continue;
25254
25740
  }
25255
- if (!fs9.existsSync(full))
25741
+ if (!fs10.existsSync(full))
25256
25742
  continue;
25257
- const text = fs9.readFileSync(full, "utf8");
25743
+ const text = fs10.readFileSync(full, "utf8");
25258
25744
  const m = text.match(/^\*\*YouTrack:\*\*\s*`?([A-Z]+-\d+)`?/m);
25259
25745
  if (m)
25260
25746
  return m[1];
@@ -25797,123 +26283,166 @@ async function runStdioServer(host, contextProvider = defaultContextProvider(hos
25797
26283
  await server.connect(new StdioServerTransport);
25798
26284
  return server;
25799
26285
  }
26286
+ // packages/workit-core/src/hooks/descriptor.ts
26287
+ var support = (descriptor, axis) => {
26288
+ if (axis.startsWith("event:"))
26289
+ return descriptor.events[axis.slice("event:".length)].support;
26290
+ if (axis === "shellPolicy.deny")
26291
+ return descriptor.shellPolicy.deny;
26292
+ if (axis === "stopControl" || axis === "shellAvailable")
26293
+ return descriptor[axis];
26294
+ const [group, key] = axis.split(".");
26295
+ return descriptor[group][key];
26296
+ };
26297
+ var usable = (value) => value === "native" || value === "partial";
26298
+ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map((rule) => {
26299
+ const levels = rule.requires.map((axis) => support(descriptor, axis));
26300
+ const available = rule.assurance !== "unavailable" && levels.every(usable) && (rule.observed ?? []).every((flag) => observed[flag] === true);
26301
+ const assurance = !available ? "unavailable" : rule.assurance === "enforced" && (levels.length === 0 || levels.some((level) => level !== "native")) ? "agent_guided" : rule.assurance;
26302
+ return {
26303
+ name: rule.name,
26304
+ surface: rule.surface,
26305
+ assurance,
26306
+ reason: assurance === "unavailable" ? rule.unavailableReason ?? rule.reason : rule.reason,
26307
+ refs: rule.refs.map((handle) => ({ kind: "host", host: descriptor.host, handle }))
26308
+ };
26309
+ });
26310
+ // packages/workit-core/src/hooks/context.ts
26311
+ var cache = new Map;
26312
+ // packages/workit-core/src/hooks/handle.ts
26313
+ var offered = new Set;
26314
+ // packages/workit-core/src/hooks/hosts/fields.ts
26315
+ var SHELLS = new Set(["sh", "bash", "zsh", "dash", "pwsh", "powershell", "cmd"]);
25800
26316
 
25801
- // packages/workit-codex/hooks/workit-hook.ts
25802
- var ref = (host, handle) => ({ kind: "host", host, handle });
26317
+ // packages/workit-core/src/hooks/hosts/claude-code.ts
26318
+ var SOURCES = new Set(["startup", "resume", "clear", "compact", "fork"]);
26319
+ var SHELL_TOOLS = new Set(["Bash", "PowerShell"]);
26320
+ var CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "SubagentStart"]);
26321
+
26322
+ // packages/workit-core/src/hooks/hosts/codex.ts
25803
26323
  function detectCodexSurface(env) {
25804
26324
  return env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || Boolean(env.CODEX_ELECTRON_RESOURCES_PATH) ? "codex_desktop" : "codex_cli";
25805
26325
  }
25806
- var codexCapabilities = (host, availability = {}) => {
25807
- const has = (key) => availability[key] === true;
25808
- return [
26326
+ var undocumented = { support: "undocumented", native: null };
26327
+ var CODEX_DESCRIPTOR = {
26328
+ host: "codex_cli",
26329
+ label: "Codex",
26330
+ verifiedAgainst: "codex-cli 0.153.4; Codex Desktop 26.901.20858",
26331
+ docs: ["https://developers.openai.com/codex/hooks"],
26332
+ transport: "hook-process",
26333
+ events: {
26334
+ "session.start": { support: "native", native: "SessionStart" },
26335
+ "context.turn": undocumented,
26336
+ "shell.pre": { support: "native", native: "PreToolUse" },
26337
+ "tool.pre": { support: "native", native: "PreToolUse" },
26338
+ "shell.post": undocumented,
26339
+ "subagent.start": { support: "native", native: "SubagentStart" },
26340
+ "subagent.stop": { support: "native", native: "SubagentStop" },
26341
+ "prompt.submit": undocumented,
26342
+ "compact.pre": { support: "none", native: null },
26343
+ stop: undocumented
26344
+ },
26345
+ shellPolicy: { deny: "native", channel: "permissionDecision", failClosed: false },
26346
+ context: {
26347
+ sessionStart: "native",
26348
+ perTurn: "undocumented",
26349
+ afterCompact: "native",
26350
+ task: "single-active"
26351
+ },
26352
+ subagents: {
26353
+ identity: "native",
26354
+ parentBinding: "none",
26355
+ blockStart: "none",
26356
+ worktreeIsolation: "undocumented",
26357
+ maxConcurrency: "undocumented"
26358
+ },
26359
+ provenance: { sessionId: "native", agentIdOnTool: "partial", postToolObserve: "undocumented" },
26360
+ interaction: { questions: "none", writeBoundary: "partial" },
26361
+ stopControl: "undocumented",
26362
+ shellAvailable: "native",
26363
+ perEventCost: "low",
26364
+ capabilities: [
25809
26365
  {
25810
26366
  name: "interactive_decision",
25811
26367
  surface: "Question",
26368
+ refs: ["Question"],
26369
+ requires: [],
25812
26370
  assurance: "agent_guided",
25813
- reason: "Codex hooks expose no native arbitrary-question answer receipt",
25814
- refs: [ref(host, "Question")]
26371
+ reason: "Codex hooks expose no native arbitrary-question answer receipt"
25815
26372
  },
25816
26373
  {
25817
26374
  name: "known_product_writes",
25818
26375
  surface: "PreToolUse",
26376
+ refs: ["PreToolUse"],
26377
+ requires: [],
25819
26378
  assurance: "unavailable",
25820
- reason: "file writes are host-policy; PreToolUse allows write tools",
25821
- refs: [ref(host, "PreToolUse")]
26379
+ reason: "file writes are host-policy; PreToolUse allows write tools"
25822
26380
  },
25823
26381
  {
25824
26382
  name: "fresh-context-review",
25825
26383
  surface: "SubagentStart",
25826
- assurance: has("subagentStart") ? "agent_guided" : "unavailable",
25827
- reason: has("subagentStart") ? "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted" : "Codex subagent lifecycle hooks are unavailable for independent review",
25828
- refs: [ref(host, "SubagentStart")]
26384
+ refs: ["SubagentStart"],
26385
+ requires: ["event:subagent.start"],
26386
+ observed: ["subagent.start"],
26387
+ assurance: "agent_guided",
26388
+ reason: "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted",
26389
+ unavailableReason: "Codex subagent lifecycle hooks are unavailable for independent review"
25829
26390
  },
25830
26391
  {
25831
26392
  name: "native_subagents",
25832
26393
  surface: "SubagentStart/SubagentStop",
25833
- assurance: has("subagentStart") && has("subagentStop") ? "agent_guided" : "unavailable",
25834
- reason: has("subagentStart") && has("subagentStop") ? "Codex reports stable child identities, but cannot block creation or bind a writer" : "Codex subagent lifecycle hooks are untrusted or incomplete",
25835
- refs: [ref(host, "SubagentStart"), ref(host, "SubagentStop")]
26394
+ refs: ["SubagentStart", "SubagentStop"],
26395
+ requires: ["event:subagent.start", "event:subagent.stop"],
26396
+ observed: ["subagent.start", "subagent.stop"],
26397
+ assurance: "agent_guided",
26398
+ reason: "Codex reports stable child identities, but cannot block creation or bind a writer",
26399
+ unavailableReason: "Codex subagent lifecycle hooks are untrusted or incomplete"
25836
26400
  },
25837
26401
  {
25838
26402
  name: "native_subagent_start",
25839
26403
  surface: "SubagentStart",
25840
- assurance: has("subagentStart") ? "agent_guided" : "unavailable",
25841
- reason: has("subagentStart") ? "SubagentStart supplies identity and bounded read-only guidance; continue:false cannot stop creation" : "SubagentStart hook is untrusted or unavailable",
25842
- refs: [ref(host, "SubagentStart")]
26404
+ refs: ["SubagentStart"],
26405
+ requires: ["event:subagent.start"],
26406
+ observed: ["subagent.start"],
26407
+ assurance: "agent_guided",
26408
+ reason: "SubagentStart supplies identity and bounded read-only guidance; continue:false cannot stop creation",
26409
+ unavailableReason: "SubagentStart hook is untrusted or unavailable"
25843
26410
  },
25844
26411
  {
25845
26412
  name: "arbitrary_shell_write",
25846
26413
  surface: "unobservable_shell",
26414
+ refs: ["PreToolUse"],
26415
+ requires: [],
25847
26416
  assurance: "unavailable",
25848
- reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete",
25849
- refs: [ref(host, "PreToolUse")]
26417
+ reason: "Only covered known tool inputs are interceptable; specialized and write_stdin paths are not complete"
25850
26418
  },
25851
26419
  {
25852
26420
  name: "compact_context",
25853
26421
  surface: "SessionStart",
25854
- assurance: has("sessionStart") ? "agent_guided" : "unavailable",
25855
- reason: "SessionStart source=compact is the single restore path",
25856
- refs: [ref(host, "SessionStart")]
26422
+ refs: ["SessionStart"],
26423
+ requires: ["context.afterCompact"],
26424
+ observed: ["session.start"],
26425
+ assurance: "agent_guided",
26426
+ reason: "SessionStart source=compact is the single restore path"
25857
26427
  }
25858
- ];
25859
- };
25860
- var events = new Set([
25861
- "SessionStart",
25862
- "PreToolUse",
25863
- "SubagentStart",
25864
- "SubagentStop"
25865
- ]);
25866
- var allowedKeys = {
25867
- SessionStart: new Set([
25868
- "hook_event_name",
25869
- "session_id",
25870
- "cwd",
25871
- "model",
25872
- "permission_mode",
25873
- "transcript_path",
25874
- "source"
25875
- ]),
25876
- PreToolUse: new Set([
25877
- "hook_event_name",
25878
- "session_id",
25879
- "cwd",
25880
- "model",
25881
- "permission_mode",
25882
- "transcript_path",
25883
- "turn_id",
25884
- "tool_name",
25885
- "tool_input",
25886
- "tool_use_id",
25887
- "agent_id",
25888
- "agent_type"
25889
- ]),
25890
- SubagentStart: new Set([
25891
- "hook_event_name",
25892
- "session_id",
25893
- "cwd",
25894
- "model",
25895
- "permission_mode",
25896
- "transcript_path",
25897
- "turn_id",
25898
- "agent_id",
25899
- "agent_type"
25900
- ]),
25901
- SubagentStop: new Set([
25902
- "hook_event_name",
25903
- "session_id",
25904
- "cwd",
25905
- "model",
25906
- "permission_mode",
25907
- "transcript_path",
25908
- "turn_id",
25909
- "agent_id",
25910
- "agent_type",
25911
- "agent_transcript_path",
25912
- "last_assistant_message",
25913
- "stop_hook_active"
25914
- ])
26428
+ ]
25915
26429
  };
25916
- var historyOfferSessions = new Set;
26430
+ var codexDescriptor = (host) => ({ ...CODEX_DESCRIPTOR, host });
26431
+ var SHELL_TOOLS2 = new Set(["bash", "unified-exec"]);
26432
+ var SOURCES2 = new Set(["startup", "resume", "clear", "compact"]);
26433
+
26434
+ // packages/workit-core/src/hooks/hosts/cursor.ts
26435
+ var BLOCKING = new Set(["preToolUse", "beforeShellExecution", "subagentStart"]);
26436
+
26437
+ // packages/workit-core/src/hooks/run.ts
26438
+ if (false) {}
26439
+ // packages/workit-codex/hooks/workit-hook.ts
26440
+ var codexCapabilities = (host, availability = {}) => capabilitiesFor(codexDescriptor(host), {
26441
+ "session.start": availability.sessionStart,
26442
+ "shell.pre": availability.preToolUse,
26443
+ "subagent.start": availability.subagentStart,
26444
+ "subagent.stop": availability.subagentStop
26445
+ });
25917
26446
  if (false)
25918
26447
  ;
25919
26448