@brainervirus/workit-codex 1.0.9 → 1.0.11

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.
@@ -6626,8 +6626,8 @@ import path15 from "node:path";
6626
6626
 
6627
6627
  // packages/workit-core/src/core.ts
6628
6628
  import { spawnSync as spawnSync2 } from "node:child_process";
6629
- import { existsSync as existsSync3, realpathSync as realpathSync5 } from "node:fs";
6630
- import path6 from "node:path";
6629
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
6630
+ import path7 from "node:path";
6631
6631
 
6632
6632
  // packages/workit-core/src/core/task-contract.ts
6633
6633
  import { createHash, randomUUID } from "node:crypto";
@@ -14419,10 +14419,12 @@ var decisionSchema = object({
14419
14419
  scope: scopeSchema,
14420
14420
  presented: text,
14421
14421
  approvedContent: text,
14422
- contentRefs: array(refSchema)
14422
+ displayed: text.optional(),
14423
+ contentRefs: array(refSchema),
14424
+ statedChoice: object({ ref: text, text }).strict().optional()
14423
14425
  }).strict(),
14424
14426
  digest,
14425
- response: _enum(["approved", "rejected"]),
14427
+ response: _enum(["approved", "rejected", "stated"]),
14426
14428
  requirementIds: array(digest),
14427
14429
  revoked: object({ at: utc, reason: text }).strict().nullable(),
14428
14430
  consumption: object({
@@ -14497,6 +14499,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
14497
14499
  path: ["decisionId"]
14498
14500
  });
14499
14501
  });
14502
+ var runtimeSchema = object({
14503
+ createdWith: text.nullable(),
14504
+ updatedWith: text
14505
+ }).strict();
14500
14506
  var taskRecordSchema = object({
14501
14507
  schemaVersion: literal(1),
14502
14508
  id,
@@ -14510,6 +14516,8 @@ var taskRecordSchema = object({
14510
14516
  status: _enum(["active", "paused", "closed"]),
14511
14517
  closure: closureSchema.nullable(),
14512
14518
  progress: progressSchema,
14519
+ pauseReason: text.nullable().optional(),
14520
+ runtime: runtimeSchema.optional(),
14513
14521
  assessments: array(entrySchema(assessmentSchema)),
14514
14522
  policy: policySchema.nullable(),
14515
14523
  policyChanges: array(policyChangeSchema),
@@ -14525,6 +14533,7 @@ var workspaceRecordSchema = object({
14525
14533
  id,
14526
14534
  revision,
14527
14535
  root: nonEmpty,
14536
+ runtime: runtimeSchema.optional(),
14528
14537
  writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
14529
14538
  }).strict();
14530
14539
  var capabilitySchema = object({
@@ -14552,6 +14561,9 @@ var taskSummarySchema = object({
14552
14561
  id,
14553
14562
  revision,
14554
14563
  workspaceRevision: revision,
14564
+ createdAt: utc,
14565
+ updatedAt: utc,
14566
+ runtime: runtimeSchema.nullable(),
14555
14567
  objective: text,
14556
14568
  status: _enum(["active", "paused", "closed"]),
14557
14569
  closure: closureSchema.nullable(),
@@ -14622,7 +14634,7 @@ var taskOperations = {
14622
14634
  expectedWorkspaceRevision: revision.optional(),
14623
14635
  outcome: outcomeSchema,
14624
14636
  summary: text,
14625
- decisionIds: array(id)
14637
+ decisionIds: array(id).optional()
14626
14638
  })
14627
14639
  };
14628
14640
  var policyOperations = {
@@ -14714,14 +14726,15 @@ var writerOperations = {
14714
14726
  ...taskId,
14715
14727
  expectedRevision: revision.optional(),
14716
14728
  expectedWorkspaceRevision: revision.optional(),
14717
- workerId: nullableId.optional()
14729
+ workerId: nullableId.optional(),
14730
+ reason: text.optional()
14718
14731
  }),
14719
14732
  release: operation({
14720
14733
  action: literal("release"),
14721
14734
  ...taskId,
14722
14735
  expectedRevision: revision.optional(),
14723
14736
  expectedWorkspaceRevision: revision.optional(),
14724
- reason: text
14737
+ reason: text.optional()
14725
14738
  })
14726
14739
  };
14727
14740
  var stateOperations = {
@@ -14928,7 +14941,26 @@ function requirementId(input) {
14928
14941
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
14929
14942
  import * as fs4 from "node:fs";
14930
14943
  import { hostname } from "node:os";
14931
- import path3 from "node:path";
14944
+ import path4 from "node:path";
14945
+
14946
+ // packages/workit-core/src/core/package-root.ts
14947
+ import { existsSync } from "node:fs";
14948
+ import path from "node:path";
14949
+ import { fileURLToPath } from "node:url";
14950
+ var packageRoot = () => {
14951
+ let dir = path.dirname(fileURLToPath(import.meta.url));
14952
+ while (true) {
14953
+ const parent = path.dirname(dir);
14954
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
14955
+ return dir;
14956
+ dir = parent;
14957
+ }
14958
+ };
14959
+ var assetRoot = () => {
14960
+ const root = packageRoot();
14961
+ const assets = path.join(root, "assets");
14962
+ return existsSync(assets) ? assets : root;
14963
+ };
14932
14964
 
14933
14965
  // node_modules/@openclaw/fs-safe/dist/errors.js
14934
14966
  var OPERATIONAL_CODES = new Set([
@@ -14979,7 +15011,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
14979
15011
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
14980
15012
  import { randomBytes } from "node:crypto";
14981
15013
  import fsSync from "node:fs";
14982
- import path from "node:path";
15014
+ import path2 from "node:path";
14983
15015
 
14984
15016
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
14985
15017
  import fs from "node:fs";
@@ -15069,13 +15101,13 @@ ${ownershipToken}
15069
15101
  return { raw, ownershipToken };
15070
15102
  }
15071
15103
  function relativeSidecarLockPath(lockRoot, lockPath) {
15072
- const resolved = path.resolve(lockPath);
15073
- const lexicalRelative = path.relative(lockRoot.rootDir, resolved);
15074
- const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path.sep}`) && !path.isAbsolute(lexicalRelative) ? lexicalRelative : path.relative(lockRoot.rootReal, resolved);
15075
- if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
15104
+ const resolved = path2.resolve(lockPath);
15105
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
15106
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
15107
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
15076
15108
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
15077
15109
  }
15078
- return relative.split(path.sep).join(path.posix.sep);
15110
+ return relative.split(path2.sep).join(path2.posix.sep);
15079
15111
  }
15080
15112
  function parseSidecarLockPayload(raw, parser) {
15081
15113
  if (parser) {
@@ -15222,7 +15254,7 @@ function getFsSafeLockConfig() {
15222
15254
 
15223
15255
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
15224
15256
  import fs3 from "node:fs";
15225
- import path2 from "node:path";
15257
+ import path3 from "node:path";
15226
15258
 
15227
15259
  // node_modules/@openclaw/fs-safe/dist/timing.js
15228
15260
  function sleepSync(ms) {
@@ -15313,26 +15345,26 @@ function canonicalLockParentSync(parent) {
15313
15345
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
15314
15346
  }
15315
15347
  function normalizeTargetPath(targetPath) {
15316
- const resolved = path2.resolve(targetPath);
15317
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
15348
+ const resolved = path3.resolve(targetPath);
15349
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
15318
15350
  try {
15319
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
15351
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
15320
15352
  } catch {
15321
15353
  return resolved;
15322
15354
  }
15323
15355
  }
15324
15356
  function boundedLockPath(lockPath, lockRoot) {
15325
- const resolved = path2.resolve(lockPath);
15357
+ const resolved = path3.resolve(lockPath);
15326
15358
  if (!lockRoot)
15327
15359
  return resolved;
15328
15360
  relativeSidecarLockPath(lockRoot, resolved);
15329
- const parent = path2.dirname(resolved);
15361
+ const parent = path3.dirname(resolved);
15330
15362
  const parentReal = canonicalLockParentSync(parent);
15331
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
15332
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
15363
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
15364
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
15333
15365
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
15334
15366
  }
15335
- return path2.join(parentReal, path2.basename(resolved));
15367
+ return path3.join(parentReal, path3.basename(resolved));
15336
15368
  }
15337
15369
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
15338
15370
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -15545,6 +15577,33 @@ var metadataLockSchema = object({
15545
15577
  nonce: string2().min(1)
15546
15578
  }).strict();
15547
15579
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
15580
+ var cachedRuntimeVersion = null;
15581
+ var runtimeVersion = () => {
15582
+ if (cachedRuntimeVersion === null) {
15583
+ try {
15584
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
15585
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
15586
+ } catch {
15587
+ cachedRuntimeVersion = "0.0.0";
15588
+ }
15589
+ }
15590
+ return cachedRuntimeVersion;
15591
+ };
15592
+ var stampNew = (record) => ({
15593
+ createdWith: record.runtime?.createdWith ?? null,
15594
+ updatedWith: runtimeVersion()
15595
+ });
15596
+ var isNewerVersion = (candidate, current) => {
15597
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
15598
+ const [next, now] = [parts(candidate), parts(current)];
15599
+ for (let index = 0;index < 3; index += 1) {
15600
+ const left = next[index] ?? 0;
15601
+ const right = now[index] ?? 0;
15602
+ if (left !== right)
15603
+ return left > right;
15604
+ }
15605
+ return false;
15606
+ };
15548
15607
  var jsonBytes = (value) => `${canonicalJson(value)}
15549
15608
  `;
15550
15609
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -15571,12 +15630,12 @@ var sameMetadataLock = (left, right) => {
15571
15630
  class TaskStore {
15572
15631
  root;
15573
15632
  constructor(root) {
15574
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
15633
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
15575
15634
  }
15576
15635
  readTask(taskId) {
15577
15636
  if (!validId(taskId))
15578
15637
  return failure("invalid_input", "task ID is invalid", { taskId });
15579
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
15638
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
15580
15639
  if (!result.exists)
15581
15640
  return failure("not_found", "task not found", { taskId });
15582
15641
  if (result.result.ok && result.result.data.id !== taskId)
@@ -15610,7 +15669,7 @@ class TaskStore {
15610
15669
  });
15611
15670
  const tasks = [];
15612
15671
  for (const name of names.sort()) {
15613
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
15672
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
15614
15673
  if (!item.exists)
15615
15674
  continue;
15616
15675
  if (!item.result.ok)
@@ -15644,7 +15703,7 @@ class TaskStore {
15644
15703
  if (!current.ok)
15645
15704
  return current;
15646
15705
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
15647
- return failure("revision_conflict", "workspace revision does not match", {
15706
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
15648
15707
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
15649
15708
  actualWorkspaceRevision: current.data?.revision ?? null
15650
15709
  });
@@ -15656,11 +15715,17 @@ class TaskStore {
15656
15715
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
15657
15716
  if (current.data && !previousWorkspaceBytes)
15658
15717
  return failure("storage_error", "workspace snapshot disappeared during creation");
15659
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
15718
+ const workspace = current.data ? {
15719
+ ...current.data,
15720
+ revision: newRevision(),
15721
+ root: this.root,
15722
+ runtime: stampNew(current.data)
15723
+ } : {
15660
15724
  schemaVersion: SCHEMA_VERSION,
15661
15725
  id: newId(),
15662
15726
  revision: newRevision(),
15663
15727
  root: this.root,
15728
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
15664
15729
  writer: null
15665
15730
  };
15666
15731
  const timestamp = value.now ?? now();
@@ -15682,6 +15747,7 @@ class TaskStore {
15682
15747
  status: "active",
15683
15748
  closure: null,
15684
15749
  progress: { summary: "", nextAction: null, blockers: [] },
15750
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
15685
15751
  assessments: [],
15686
15752
  policy: null,
15687
15753
  policyChanges: [],
@@ -15712,7 +15778,7 @@ class TaskStore {
15712
15778
  if (!current.ok)
15713
15779
  return current;
15714
15780
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
15715
- return failure("revision_conflict", "workspace revision does not match", {
15781
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
15716
15782
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
15717
15783
  actualWorkspaceRevision: current.data?.revision ?? null
15718
15784
  });
@@ -15720,11 +15786,17 @@ class TaskStore {
15720
15786
  if (current.data && !previousWorkspaceBytes)
15721
15787
  return failure("storage_error", "workspace snapshot disappeared during import");
15722
15788
  const timestamp = input.now ?? now();
15723
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
15789
+ const workspace = current.data ? {
15790
+ ...current.data,
15791
+ revision: newRevision(),
15792
+ root: this.root,
15793
+ runtime: stampNew(current.data)
15794
+ } : {
15724
15795
  schemaVersion: SCHEMA_VERSION,
15725
15796
  id: input.workspaceId ?? newId(),
15726
15797
  revision: newRevision(),
15727
15798
  root: this.root,
15799
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
15728
15800
  writer: null
15729
15801
  };
15730
15802
  const task = {
@@ -15732,7 +15804,11 @@ class TaskStore {
15732
15804
  workspaceId: workspace.id,
15733
15805
  revision: newRevision(),
15734
15806
  createdAt: timestamp,
15735
- updatedAt: timestamp
15807
+ updatedAt: timestamp,
15808
+ runtime: {
15809
+ createdWith: input.task.runtime?.createdWith ?? null,
15810
+ updatedWith: runtimeVersion()
15811
+ }
15736
15812
  };
15737
15813
  const validTask = taskRecordSchema.safeParse(task);
15738
15814
  if (!validTask.success)
@@ -15781,7 +15857,8 @@ class TaskStore {
15781
15857
  workspaceId: current.data.workspaceId,
15782
15858
  createdAt: current.data.createdAt,
15783
15859
  revision: context.revision,
15784
- updatedAt: context.now
15860
+ updatedAt: context.now,
15861
+ runtime: stampNew(current.data)
15785
15862
  };
15786
15863
  const valid = taskRecordSchema.safeParse(record);
15787
15864
  if (!valid.success)
@@ -15815,7 +15892,8 @@ class TaskStore {
15815
15892
  ...changed.data,
15816
15893
  id: current.data.id,
15817
15894
  root: this.root,
15818
- revision: context.revision
15895
+ revision: context.revision,
15896
+ runtime: stampNew(current.data)
15819
15897
  };
15820
15898
  const valid = workspaceRecordSchema.safeParse(record);
15821
15899
  if (!valid.success)
@@ -15858,7 +15936,8 @@ class TaskStore {
15858
15936
  ...changedWorkspace.data,
15859
15937
  id: workspace.data.id,
15860
15938
  root: this.root,
15861
- revision: workspaceContext.revision
15939
+ revision: workspaceContext.revision,
15940
+ runtime: stampNew(workspace.data)
15862
15941
  });
15863
15942
  if (!nextWorkspace.success)
15864
15943
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -15883,7 +15962,8 @@ class TaskStore {
15883
15962
  workspaceId: task.data.workspaceId,
15884
15963
  createdAt: task.data.createdAt,
15885
15964
  revision: taskContext.revision,
15886
- updatedAt: taskContext.now
15965
+ updatedAt: taskContext.now,
15966
+ runtime: stampNew(task.data)
15887
15967
  });
15888
15968
  if (!nextTask.success) {
15889
15969
  this.markUncertain(nextWorkspace.data);
@@ -15910,7 +15990,7 @@ class TaskStore {
15910
15990
  if (match)
15911
15991
  candidates.push({
15912
15992
  target: match[1],
15913
- path: path3.join(this.recoveryDir, name),
15993
+ path: path4.join(this.recoveryDir, name),
15914
15994
  digest: match[3]
15915
15995
  });
15916
15996
  }
@@ -16103,7 +16183,8 @@ class TaskStore {
16103
16183
  const value = {
16104
16184
  ...workspace,
16105
16185
  writer: { ...workspace.writer, state: "uncertain" },
16106
- revision: newRevision()
16186
+ revision: newRevision(),
16187
+ runtime: stampNew(workspace)
16107
16188
  };
16108
16189
  this.replaceSnapshot(this.workspacePath, value, workspace);
16109
16190
  }
@@ -16181,7 +16262,7 @@ class TaskStore {
16181
16262
  }
16182
16263
  fs4.renameSync(temporary, file);
16183
16264
  temporary = undefined;
16184
- this.fsyncDirectory(path3.dirname(file));
16265
+ this.fsyncDirectory(path4.dirname(file));
16185
16266
  return success(null, null, value);
16186
16267
  } catch (error) {
16187
16268
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -16195,9 +16276,9 @@ class TaskStore {
16195
16276
  }
16196
16277
  }
16197
16278
  saveRecovery(file, bytes) {
16198
- const target = path3.basename(file) === "workspace.json" ? "workspace" : "task";
16199
- const id = target === "task" ? path3.basename(file, ".json") : "workspace";
16200
- const destination = path3.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
16279
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
16280
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
16281
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
16201
16282
  if (fs4.existsSync(destination)) {
16202
16283
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
16203
16284
  return;
@@ -16275,7 +16356,7 @@ class TaskStore {
16275
16356
  for (const name of fs4.readdirSync(this.tasksDir)) {
16276
16357
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
16277
16358
  return null;
16278
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
16359
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
16279
16360
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
16280
16361
  return null;
16281
16362
  ids.add(parsed.data.workspaceId);
@@ -16317,7 +16398,12 @@ class TaskStore {
16317
16398
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
16318
16399
  return failure("unsupported_version", "unsupported snapshot schema version");
16319
16400
  const parsed = schema.safeParse(value);
16320
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
16401
+ if (parsed.success)
16402
+ return success(null, null, parsed.data);
16403
+ const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
16404
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
16405
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
16406
+ return failure("recovery_required", "snapshot does not satisfy its schema");
16321
16407
  }
16322
16408
  processStart(pid) {
16323
16409
  try {
@@ -16327,43 +16413,98 @@ class TaskStore {
16327
16413
  }
16328
16414
  }
16329
16415
  conflict(expected, actual) {
16330
- return failure("revision_conflict", "snapshot revision does not match", {
16416
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
16331
16417
  expectedRevision: expected,
16332
16418
  actualRevision: actual
16333
16419
  });
16334
16420
  }
16335
16421
  taskPath(taskId) {
16336
- return path3.join(this.tasksDir, `${taskId}.json`);
16422
+ return path4.join(this.tasksDir, `${taskId}.json`);
16337
16423
  }
16338
16424
  get workitDir() {
16339
- return path3.join(this.root, ".workit");
16425
+ return path4.join(this.root, ".workit");
16340
16426
  }
16341
16427
  get tasksDir() {
16342
- return path3.join(this.workitDir, "tasks");
16428
+ return path4.join(this.workitDir, "tasks");
16343
16429
  }
16344
16430
  get recoveryDir() {
16345
- return path3.join(this.workitDir, "recovery");
16431
+ return path4.join(this.workitDir, "recovery");
16346
16432
  }
16347
16433
  get workspacePath() {
16348
- return path3.join(this.workitDir, "workspace.json");
16434
+ return path4.join(this.workitDir, "workspace.json");
16349
16435
  }
16350
16436
  get lockPath() {
16351
- return path3.join(this.workitDir, "metadata.lock");
16437
+ return path4.join(this.workitDir, "metadata.lock");
16352
16438
  }
16353
16439
  get gitignorePath() {
16354
- return path3.join(this.workitDir, ".gitignore");
16440
+ return path4.join(this.workitDir, ".gitignore");
16355
16441
  }
16356
16442
  }
16443
+ // packages/workit-core/src/core/workers.ts
16444
+ var same = (left, right) => {
16445
+ try {
16446
+ return canonicalJson(left) === canonicalJson(right);
16447
+ } catch {
16448
+ return false;
16449
+ }
16450
+ };
16451
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
16452
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
16453
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
16454
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
16455
+ const code = char.charCodeAt(0);
16456
+ return code < 32 || code === 127;
16457
+ }) && !value.split(/[\\/]/).includes("..");
16458
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
16459
+ var workerIdOf = (value) => value.workerId ?? null;
16460
+ function assertProductWriteAllowed(input) {
16461
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
16462
+ return failure("invalid_input", "invalid product write path");
16463
+ if (!input.store)
16464
+ return failure("permission_denied", "write authorization requires core state");
16465
+ const task = input.store.readTask(input.task.id);
16466
+ if (!task.ok)
16467
+ return task;
16468
+ const workspace = input.store.readWorkspace();
16469
+ if (!workspace.ok)
16470
+ return workspace;
16471
+ if (!workspace.data)
16472
+ return failure("not_found", "workspace not found");
16473
+ const workspaceRecord = workspace.data;
16474
+ if (task.data.status !== "active")
16475
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
16476
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
16477
+ return failure("recovery_required", "task and workspace bindings are invalid");
16478
+ const writer = workspaceRecord.writer;
16479
+ if (!writer)
16480
+ return failure("permission_denied", "checkout has no writer owner");
16481
+ if (writer.state === "uncertain")
16482
+ return failure("recovery_required", "checkout writer requires recovery", {
16483
+ owner: writer.owner
16484
+ });
16485
+ const caller = callerValue(input.caller);
16486
+ const workerId = workerIdOf(input.caller);
16487
+ const owner = writer.owner;
16488
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
16489
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
16490
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
16491
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
16492
+ return failure("permission_denied", "only implementers can own product writes");
16493
+ 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))))
16494
+ return failure("recovery_required", "worker session is not an active writer");
16495
+ return success(null, workspaceRecord.revision, owner);
16496
+ }
16497
+
16357
16498
  // packages/workit-core/src/core/task-evaluation.ts
16358
16499
  import { createHash as createHash4 } from "node:crypto";
16359
16500
  import * as fs6 from "node:fs";
16360
16501
  import { spawnSync } from "node:child_process";
16361
- import path5 from "node:path";
16502
+ import path6 from "node:path";
16362
16503
 
16363
16504
  // packages/workit-core/src/core/authority.ts
16364
16505
  import { createHash as createHash3 } from "node:crypto";
16365
16506
  import * as fs5 from "node:fs";
16366
- import path4 from "node:path";
16507
+ import path5 from "node:path";
16367
16508
  var verifiedAuthorities = new WeakMap;
16368
16509
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
16369
16510
  const token = {};
@@ -16490,20 +16631,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
16490
16631
  continue;
16491
16632
  if (!reference.digest)
16492
16633
  return failure("invalid_input", "document references require a byte digest");
16493
- const target = path4.resolve(root, reference.path);
16494
- if (target !== root && !target.startsWith(`${root}${path4.sep}`))
16495
- return failure("invalid_input", "document reference escapes checkout");
16634
+ const target = path5.resolve(root, reference.path);
16635
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
16636
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16637
+ fields: [
16638
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
16639
+ ]
16640
+ });
16496
16641
  try {
16497
- const relative = path4.relative(root, target);
16642
+ const relative = path5.relative(root, target);
16498
16643
  let current = root;
16499
- for (const segment of relative.split(path4.sep)) {
16644
+ for (const segment of relative.split(path5.sep)) {
16500
16645
  if (!segment)
16501
16646
  continue;
16502
- current = path4.join(current, segment);
16647
+ current = path5.join(current, segment);
16503
16648
  const stat = fs5.lstatSync(current);
16504
16649
  const real = fs5.realpathSync(current);
16505
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
16506
- return failure("invalid_input", "document reference escapes checkout");
16650
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
16651
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
16652
+ fields: [
16653
+ {
16654
+ path: "contentRefs.path",
16655
+ reason: `${reference.path} resolves outside the checkout`
16656
+ }
16657
+ ]
16658
+ });
16507
16659
  if (stat.isSymbolicLink())
16508
16660
  return failure("invalid_input", "document reference uses a symlink");
16509
16661
  if (current === target && !stat.isFile())
@@ -16850,14 +17002,14 @@ var verifyDecisionContent = verifyContentRefs;
16850
17002
 
16851
17003
  // packages/workit-core/src/core/task-evaluation.ts
16852
17004
  var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
16853
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path5.sep}`);
16854
- var relative = (root, candidate) => path5.relative(root, candidate).split(path5.sep).join("/") || ".";
17005
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
17006
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
16855
17007
  var canonicalPath = (value) => {
16856
17008
  if (typeof value !== "string" || !value)
16857
17009
  return null;
16858
17010
  if (value.includes("\\") || value.split("/").includes(".."))
16859
17011
  return null;
16860
- const normalized = path5.posix.normalize(value);
17012
+ const normalized = path6.posix.normalize(value);
16861
17013
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
16862
17014
  return null;
16863
17015
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -16905,14 +17057,14 @@ var gitPaths = (root) => {
16905
17057
  git: /not a git repository/i.test(stderr) ? false : true
16906
17058
  };
16907
17059
  }
16908
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
17060
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
16909
17061
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
16910
17062
  if (stagedDeleted.status !== 0) {
16911
17063
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
16912
17064
  if (!/not a git repository/i.test(stderr))
16913
17065
  return { paths, uncertain: true, git: true };
16914
17066
  } else if (stagedDeleted.stdout) {
16915
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
17067
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
16916
17068
  }
16917
17069
  return { paths: [...new Set(paths)], uncertain: false, git: true };
16918
17070
  };
@@ -16927,7 +17079,7 @@ var walk = (root, directory, output) => {
16927
17079
  for (const name of names) {
16928
17080
  if (name === ".git" || name === ".workit")
16929
17081
  continue;
16930
- const target = path5.join(directory, name);
17082
+ const target = path6.join(directory, name);
16931
17083
  let stat;
16932
17084
  try {
16933
17085
  stat = fs6.lstatSync(target);
@@ -16945,17 +17097,17 @@ var walk = (root, directory, output) => {
16945
17097
  var scopeRoots = (root, scope) => {
16946
17098
  const roots = [];
16947
17099
  for (const item of scope.paths.length ? scope.paths : ["."]) {
16948
- const target = path5.resolve(root, item);
17100
+ const target = path6.resolve(root, item);
16949
17101
  if (!inside(root, target))
16950
- return failure("invalid_input", "candidate scope escapes checkout");
17102
+ return failure("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` }] });
16951
17103
  let ancestor = target;
16952
17104
  while (!fs6.existsSync(ancestor) && ancestor !== root)
16953
- ancestor = path5.dirname(ancestor);
17105
+ ancestor = path6.dirname(ancestor);
16954
17106
  try {
16955
17107
  if (!inside(root, fs6.realpathSync(ancestor)))
16956
- return failure("invalid_input", "candidate scope escapes checkout");
17108
+ return failure("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` }] });
16957
17109
  } catch {
16958
- return failure("invalid_input", "candidate scope cannot be inspected");
17110
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
16959
17111
  }
16960
17112
  roots.push(target);
16961
17113
  }
@@ -16970,7 +17122,7 @@ function captureCandidate(root, scope, environment = []) {
16970
17122
  }
16971
17123
  const normalizedScope = canonicalScope(scope);
16972
17124
  if (!normalizedScope)
16973
- return failure("invalid_input", "candidate scope is invalid");
17125
+ return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
16974
17126
  const roots = scopeRoots(checkout, normalizedScope);
16975
17127
  if (!roots.ok)
16976
17128
  return roots;
@@ -17002,7 +17154,7 @@ function captureCandidate(root, scope, environment = []) {
17002
17154
  for (const item of [...names].sort()) {
17003
17155
  if (!scopeMatches(item, normalizedScope))
17004
17156
  continue;
17005
- const target = path5.join(checkout, item);
17157
+ const target = path6.join(checkout, item);
17006
17158
  try {
17007
17159
  const stat = fs6.lstatSync(target);
17008
17160
  if (stat.isSymbolicLink()) {
@@ -17124,7 +17276,7 @@ function evaluateEvidence(task, candidate) {
17124
17276
  }
17125
17277
  var scopeCovers2 = scopeCovers;
17126
17278
  var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
17127
- var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
17279
+ var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
17128
17280
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
17129
17281
  if (kind !== "passed")
17130
17282
  return false;
@@ -17159,7 +17311,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
17159
17311
  return true;
17160
17312
  const reviewSession = sessionFromRef(entry.data.reviewContext);
17161
17313
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
17162
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
17314
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
17163
17315
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
17164
17316
  });
17165
17317
  if (passed.length) {
@@ -17200,7 +17352,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
17200
17352
  status: "satisfied",
17201
17353
  evidenceIds: [],
17202
17354
  decisionIds: decisions.map((decision) => decision.id),
17203
- reason: "an applicable approved decision satisfies the requirement"
17355
+ reason: "an applicable approved or stated decision satisfies the requirement"
17204
17356
  };
17205
17357
  const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
17206
17358
  if (limitations.length)
@@ -17255,7 +17407,17 @@ function evaluateClosure(requestedOutcome, view) {
17255
17407
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
17256
17408
  if (requestedOutcome !== "stopped" && blocking.length)
17257
17409
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
17258
- requirementIds: blocking.map((item) => item.requirementId)
17410
+ requirementIds: blocking.map((item) => item.requirementId),
17411
+ requirements: blocking.map((item) => {
17412
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
17413
+ return {
17414
+ requirementId: item.requirementId,
17415
+ ruleId: requirement?.ruleId ?? "unknown",
17416
+ reason: item.reason,
17417
+ satisfaction: requirement?.satisfaction ?? "",
17418
+ dependentAction: requirement?.dependentAction ?? null
17419
+ };
17420
+ })
17259
17421
  });
17260
17422
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
17261
17423
  if (requestedOutcome === "verified" && accepted.length)
@@ -17291,7 +17453,7 @@ function reconcileResume(view, observations = []) {
17291
17453
  return candidate;
17292
17454
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
17293
17455
  const blockers = [...view.task.progress.blockers];
17294
- if (view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
17456
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
17295
17457
  blockers.push({
17296
17458
  reason: "worker state requires reconciliation",
17297
17459
  dependentAction: "resume",
@@ -17324,7 +17486,11 @@ var externalActionSchema = discriminatedUnion("operation", [
17324
17486
  }).strict(),
17325
17487
  object({
17326
17488
  operation: literal("git.commit"),
17327
- payload: object({ message: string2().min(1) }).strict()
17489
+ payload: object({
17490
+ message: string2().min(1).optional(),
17491
+ plan_steps: array(string2().min(1)).optional(),
17492
+ plan_branch: string2().min(1).optional()
17493
+ }).strict()
17328
17494
  }).strict(),
17329
17495
  object({
17330
17496
  operation: literal("git.push"),
@@ -17559,7 +17725,7 @@ function resolveRequirements(input) {
17559
17725
  ].join(", ")}`,
17560
17726
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
17561
17727
  before: "dependent_action",
17562
- dependentAction: "dependent-action",
17728
+ dependentAction: null,
17563
17729
  acceptanceAllowed: false
17564
17730
  }));
17565
17731
  if (signals.productChoiceOpen.value === true)
@@ -17570,7 +17736,7 @@ function resolveRequirements(input) {
17570
17736
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
17571
17737
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
17572
17738
  before: "dependent_action",
17573
- dependentAction: "dependent-action",
17739
+ dependentAction: null,
17574
17740
  acceptanceAllowed: false
17575
17741
  }));
17576
17742
  if (signals.behaviorChange.value === true)
@@ -17728,59 +17894,6 @@ function diffPolicy(previous, next, reason, now) {
17728
17894
  return parsed.data;
17729
17895
  }
17730
17896
 
17731
- // packages/workit-core/src/core/workers.ts
17732
- var same = (left, right) => {
17733
- try {
17734
- return canonicalJson(left) === canonicalJson(right);
17735
- } catch {
17736
- return false;
17737
- }
17738
- };
17739
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
17740
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
17741
- const code = char.charCodeAt(0);
17742
- return code < 32 || code === 127;
17743
- }) && !value.split(/[\\/]/).includes("..");
17744
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
17745
- var workerIdOf = (value) => value.workerId ?? null;
17746
- function assertProductWriteAllowed(input) {
17747
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
17748
- return failure("invalid_input", "invalid product write path");
17749
- if (!input.store)
17750
- return failure("permission_denied", "write authorization requires core state");
17751
- const task = input.store.readTask(input.task.id);
17752
- if (!task.ok)
17753
- return task;
17754
- const workspace = input.store.readWorkspace();
17755
- if (!workspace.ok)
17756
- return workspace;
17757
- if (!workspace.data)
17758
- return failure("not_found", "workspace not found");
17759
- const workspaceRecord = workspace.data;
17760
- if (task.data.status !== "active")
17761
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
17762
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
17763
- return failure("recovery_required", "task and workspace bindings are invalid");
17764
- const writer = workspaceRecord.writer;
17765
- if (!writer)
17766
- return failure("permission_denied", "checkout has no writer owner");
17767
- if (writer.state === "uncertain")
17768
- return failure("recovery_required", "checkout writer requires recovery", {
17769
- owner: writer.owner
17770
- });
17771
- const caller = callerValue(input.caller);
17772
- const workerId = workerIdOf(input.caller);
17773
- const owner = writer.owner;
17774
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
17775
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
17776
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
17777
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
17778
- return failure("permission_denied", "only implementers can own product writes");
17779
- 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))))
17780
- return failure("recovery_required", "worker session is not an active writer");
17781
- return success(null, workspaceRecord.revision, owner);
17782
- }
17783
-
17784
17897
  // packages/workit-core/src/core/task-engine.ts
17785
17898
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
17786
17899
  kind,
@@ -17911,15 +18024,20 @@ var applyWorkerLifecycle = (input) => {
17911
18024
  return failure("invalid_transition", "worker has not started");
17912
18025
  if (input.state === "running" && entry.data.state === "stopped")
17913
18026
  return failure("invalid_transition", "stopped worker cannot run again");
18027
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
17914
18028
  const nextEntry = {
17915
18029
  ...entry,
17916
18030
  recordedAt: input.now ?? entry.recordedAt,
17917
18031
  provenance: authority.provenance,
17918
- data: { ...entry.data, state: input.state, session: input.session }
18032
+ data: {
18033
+ ...entry.data,
18034
+ state: terminalCancel ? "cancelling" : input.state,
18035
+ session: input.session
18036
+ }
17919
18037
  };
17920
18038
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
17921
18039
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
17922
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
18040
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
17923
18041
  return success(task.data.revision, workspace.data.revision, entry);
17924
18042
  const changed = input.store.mutateTaskAndWorkspace({
17925
18043
  taskId: task.data.id,
@@ -18289,7 +18407,7 @@ class WorkitCore {
18289
18407
  activeWorkerBlocker(task, workspace) {
18290
18408
  if (workspace.writer)
18291
18409
  return failure("recovery_required", "writer ownership must be released first");
18292
- if (task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
18410
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
18293
18411
  return failure("recovery_required", "worker state requires reconciliation");
18294
18412
  return null;
18295
18413
  }
@@ -18526,11 +18644,6 @@ class WorkitCore {
18526
18644
  if (helper && !helper.ok)
18527
18645
  return helper;
18528
18646
  const evidence = input.evidence;
18529
- if (helper?.ok) {
18530
- const assignment = helper.data.data.assignment;
18531
- if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
18532
- return failure("permission_denied", "evidence is outside the worker assignment");
18533
- }
18534
18647
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
18535
18648
  return failure("invalid_input", "missing or skipped evidence requires a reason");
18536
18649
  if (evidence.kind === "review") {
@@ -18547,6 +18660,11 @@ class WorkitCore {
18547
18660
  evidence.beforeCandidateId ??= null;
18548
18661
  evidence.candidateId ??= null;
18549
18662
  }
18663
+ if (helper?.ok) {
18664
+ const assignment = helper.data.data.assignment;
18665
+ if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
18666
+ return failure("permission_denied", "evidence is outside the worker assignment");
18667
+ }
18550
18668
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
18551
18669
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
18552
18670
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -18600,7 +18718,12 @@ class WorkitCore {
18600
18718
  const input = parsed.data;
18601
18719
  if (nativeRequired && input.action !== "record")
18602
18720
  return failure("permission_denied", "native observation is only valid for recording decisions");
18603
- if (nativeRequired && nativeObservation === undefined)
18721
+ const stated = input.action === "record" && input.response === "stated";
18722
+ if (stated && input.purpose === "action")
18723
+ return failure("permission_denied", "stated choices cannot authorize mutating actions");
18724
+ if (stated && !input.binding?.statedChoice)
18725
+ return failure("invalid_input", "stated choices require binding.statedChoice");
18726
+ if (nativeRequired && nativeObservation === undefined && !stated)
18604
18727
  return failure("permission_denied", "native decision observation is required");
18605
18728
  const task = this.store.readTask(input.taskId);
18606
18729
  if (!task.ok)
@@ -18633,7 +18756,7 @@ class WorkitCore {
18633
18756
  consumption: null
18634
18757
  };
18635
18758
  const data = { ...base, digest: decisionDigest(base) };
18636
- const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
18759
+ const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
18637
18760
  observation: nativeObservation,
18638
18761
  expected: {
18639
18762
  taskId: task.data.id,
@@ -19442,6 +19565,9 @@ class WorkitCore {
19442
19565
  id: task.id,
19443
19566
  revision: task.revision,
19444
19567
  workspaceRevision: workspace.data.revision,
19568
+ createdAt: task.createdAt,
19569
+ updatedAt: task.updatedAt,
19570
+ runtime: task.runtime ?? null,
19445
19571
  objective: task.intent.data.objective,
19446
19572
  status: task.status,
19447
19573
  closure: task.closure,
@@ -19527,7 +19653,7 @@ class WorkitCore {
19527
19653
  const base = reconcileResume(effectiveView);
19528
19654
  if (!base.ok)
19529
19655
  return base;
19530
- const needsReconciliation = view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
19656
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
19531
19657
  const blockers = [...base.data.blockers];
19532
19658
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
19533
19659
  blockers.push({
@@ -19594,7 +19720,8 @@ class WorkitCore {
19594
19720
  ...current,
19595
19721
  status,
19596
19722
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
19597
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
19723
+ progress: current.progress,
19724
+ pauseReason: status === "paused" ? input.reason ?? null : null
19598
19725
  })
19599
19726
  });
19600
19727
  if (!changed.ok)
@@ -19630,7 +19757,7 @@ class WorkitCore {
19630
19757
  return workspace;
19631
19758
  if (!workspace.data)
19632
19759
  return failure("not_found", "workspace not found");
19633
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
19760
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
19634
19761
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
19635
19762
  this.fillRevisions(input, task.data, workspace.data);
19636
19763
  const changed = this.store.mutateTaskAndWorkspace({
@@ -19741,15 +19868,15 @@ function resolveGitRevision(root, value) {
19741
19868
  }
19742
19869
  function resolveInside(root, candidate) {
19743
19870
  const base = realpathSync5(root);
19744
- const target = path6.resolve(base, candidate);
19745
- if (target !== base && !target.startsWith(base + path6.sep)) {
19871
+ const target = path7.resolve(base, candidate);
19872
+ if (target !== base && !target.startsWith(base + path7.sep)) {
19746
19873
  throw new Error("path must stay inside repository root");
19747
19874
  }
19748
19875
  let ancestor = target;
19749
- while (!existsSync3(ancestor))
19750
- ancestor = path6.dirname(ancestor);
19876
+ while (!existsSync4(ancestor))
19877
+ ancestor = path7.dirname(ancestor);
19751
19878
  const canonicalAncestor = realpathSync5(ancestor);
19752
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path6.sep)) {
19879
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
19753
19880
  throw new Error("path must stay inside repository root");
19754
19881
  }
19755
19882
  return target;
@@ -25796,14 +25923,14 @@ var gitContext = (workspaceRoot, paths = []) => {
25796
25923
  import {
25797
25924
  copyFileSync,
25798
25925
  cpSync,
25799
- existsSync as existsSync4,
25926
+ existsSync as existsSync5,
25800
25927
  mkdirSync as mkdirSync2,
25801
25928
  readFileSync as readFileSync4,
25802
25929
  readdirSync as readdirSync3,
25803
25930
  writeFileSync as writeFileSync2
25804
25931
  } from "node:fs";
25805
25932
  import os2 from "node:os";
25806
- import path7 from "node:path";
25933
+ import path8 from "node:path";
25807
25934
  var diagnosticLogger;
25808
25935
  var PRESETS = {
25809
25936
  gitflow: {
@@ -25824,7 +25951,7 @@ var mergePreset = (preset, input = {}, current = {
25824
25951
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
25825
25952
  };
25826
25953
  };
25827
- var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path7.join(process.env.XDG_CONFIG_HOME || path7.join(os2.homedir(), ".config"), "workit");
25954
+ var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workit");
25828
25955
  var migratedDir = null;
25829
25956
  var migrationFailed = false;
25830
25957
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -25834,12 +25961,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25834
25961
  migratedDir = dir;
25835
25962
  return dir;
25836
25963
  }
25837
- const legacy = path7.join(process.env.XDG_CONFIG_HOME || path7.join(os2.homedir(), ".config"), "workflow-toolkit");
25838
- if (!existsSync4(legacy)) {
25964
+ const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
25965
+ if (!existsSync5(legacy)) {
25839
25966
  migratedDir = dir;
25840
25967
  return dir;
25841
25968
  }
25842
- if (!migrationFailed && existsSync4(dir)) {
25969
+ if (!migrationFailed && existsSync5(dir)) {
25843
25970
  migratedDir = dir;
25844
25971
  return dir;
25845
25972
  }
@@ -25847,9 +25974,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25847
25974
  mkdirSync2(dir, { recursive: true });
25848
25975
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
25849
25976
  for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
25850
- const src = path7.join(legacy, entry.name);
25851
- const dest = path7.join(dir, entry.name);
25852
- if (existsSync4(dest))
25977
+ const src = path8.join(legacy, entry.name);
25978
+ const dest = path8.join(dir, entry.name);
25979
+ if (existsSync5(dest))
25853
25980
  continue;
25854
25981
  try {
25855
25982
  if (entry.isDirectory())
@@ -25922,7 +26049,7 @@ var parseConfigResult = (raw, file) => {
25922
26049
  };
25923
26050
  };
25924
26051
  var readConfigTyped = (dir) => {
25925
- const file = path7.join(dir ?? configDir(), "config.json");
26052
+ const file = path8.join(dir ?? configDir(), "config.json");
25926
26053
  return parseConfigResult(readSafe(file), file);
25927
26054
  };
25928
26055
  var readConfig = () => {
@@ -25958,9 +26085,9 @@ var COMMIT_PRESETS = [
25958
26085
 
25959
26086
  // packages/workit-core/src/core/workspaces.ts
25960
26087
  import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
25961
- import path8 from "node:path";
26088
+ import path9 from "node:path";
25962
26089
  var readWorkspacesResult = (dir = configDir()) => {
25963
- const file = path8.join(dir, "workspaces.json");
26090
+ const file = path9.join(dir, "workspaces.json");
25964
26091
  let raw;
25965
26092
  try {
25966
26093
  raw = readFileSync5(file, "utf8");
@@ -26058,10 +26185,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
26058
26185
 
26059
26186
  // packages/workit-core/src/core/vcs-config.ts
26060
26187
  import fs7 from "node:fs";
26061
- import path9 from "node:path";
26188
+ import path10 from "node:path";
26062
26189
  import { spawnSync as spawnSync3 } from "node:child_process";
26063
26190
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
26064
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
26191
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
26065
26192
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
26066
26193
  var remoteProvider = (cwd) => {
26067
26194
  const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
@@ -26144,8 +26271,8 @@ function vcsConfig(mode, cwd) {
26144
26271
  }
26145
26272
  const prov = cfg[provider] ?? {};
26146
26273
  const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
26147
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path9.join(configDir(), `${provider}.token`));
26148
- const tokenPath = path9.resolve(tokenFile);
26274
+ const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
26275
+ const tokenPath = path10.resolve(tokenFile);
26149
26276
  let tokenOk = false;
26150
26277
  if (fs7.existsSync(tokenPath)) {
26151
26278
  const token = fs7.readFileSync(tokenPath, "utf8").trim();
@@ -26154,7 +26281,7 @@ function vcsConfig(mode, cwd) {
26154
26281
  }
26155
26282
  const out = {
26156
26283
  ok: true,
26157
- configPath: path9.resolve(cfgPath),
26284
+ configPath: path10.resolve(cfgPath),
26158
26285
  provider,
26159
26286
  defaultTargetBranch: defaultTarget,
26160
26287
  pr: cfg.pr ?? {},
@@ -26247,18 +26374,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
26247
26374
 
26248
26375
  // packages/workit-core/src/core/pr-create.ts
26249
26376
  import fs8 from "node:fs";
26250
- import path10 from "node:path";
26377
+ import path11 from "node:path";
26251
26378
  function parseGhIssue(value) {
26252
26379
  const m = /issues\/(\d+)/.exec(value);
26253
26380
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26254
26381
  }
26255
26382
  function whichOnPath(tool) {
26256
26383
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26257
- for (const dir of (process.env.PATH ?? "").split(path10.delimiter)) {
26384
+ for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
26258
26385
  if (!dir)
26259
26386
  continue;
26260
26387
  for (const name of names) {
26261
- const candidate = path10.join(dir, name);
26388
+ const candidate = path11.join(dir, name);
26262
26389
  try {
26263
26390
  fs8.accessSync(candidate, fs8.constants.X_OK);
26264
26391
  return candidate;
@@ -26271,8 +26398,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
26271
26398
 
26272
26399
  // packages/workit-core/src/core/repo-context.ts
26273
26400
  import { spawnSync as spawnSync4 } from "node:child_process";
26274
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26275
- import path11 from "node:path";
26401
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26402
+ import path12 from "node:path";
26276
26403
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26277
26404
  try {
26278
26405
  gitRevisionParts(value);
@@ -26406,7 +26533,7 @@ var findPrTemplate = (cwd) => {
26406
26533
  ]) {
26407
26534
  const parts = rel.split("/");
26408
26535
  const base = parts[parts.length - 1];
26409
- const dir = parts.length > 1 ? path11.join(cwd, ...parts.slice(0, -1)) : cwd;
26536
+ const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
26410
26537
  const actual = probeCaseInsensitive(dir, base);
26411
26538
  if (actual)
26412
26539
  return [...parts.slice(0, -1), actual].join("/");
@@ -26437,8 +26564,8 @@ var diffStatForRange = (cwd, range) => {
26437
26564
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26438
26565
  };
26439
26566
  var packageScripts = (cwd, keys) => {
26440
- const pkgPath = path11.join(cwd, "package.json");
26441
- if (!existsSync5(pkgPath))
26567
+ const pkgPath = path12.join(cwd, "package.json");
26568
+ if (!existsSync6(pkgPath))
26442
26569
  return [];
26443
26570
  try {
26444
26571
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
@@ -26463,9 +26590,9 @@ var documentationFiles = (cwd) => {
26463
26590
  if (entry.isDirectory()) {
26464
26591
  if (excluded.has(entry.name))
26465
26592
  continue;
26466
- walk(path11.join(dir, entry.name), depth + 1);
26593
+ walk(path12.join(dir, entry.name), depth + 1);
26467
26594
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26468
- matches.push("./" + path11.relative(cwd, path11.join(dir, entry.name)).replaceAll("\\", "/"));
26595
+ matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
26469
26596
  }
26470
26597
  }
26471
26598
  };
@@ -26473,7 +26600,7 @@ var documentationFiles = (cwd) => {
26473
26600
  return matches.sort().slice(0, 200);
26474
26601
  };
26475
26602
  var readTrimmed = (file, maxLines) => {
26476
- if (!existsSync5(file))
26603
+ if (!existsSync6(file))
26477
26604
  return "";
26478
26605
  const lines = readFileSync6(file, "utf8").split(`
26479
26606
  `);
@@ -26481,7 +26608,7 @@ var readTrimmed = (file, maxLines) => {
26481
26608
  `);
26482
26609
  };
26483
26610
  function prReadyContext(root, range) {
26484
- const cwd = path11.resolve(repoRoot(root));
26611
+ const cwd = path12.resolve(repoRoot(root));
26485
26612
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26486
26613
  return invalidRange(cwd);
26487
26614
  let stdout = "";
@@ -26539,7 +26666,7 @@ ${status.stderr}` : "");
26539
26666
  stdout += `template_path: ${template}
26540
26667
 
26541
26668
  `;
26542
- stdout += readTrimmed(path11.join(cwd, template), 220);
26669
+ stdout += readTrimmed(path12.join(cwd, template), 220);
26543
26670
  } else {
26544
26671
  stdout += `template_path: none
26545
26672
 
@@ -26549,7 +26676,7 @@ ${status.stderr}` : "");
26549
26676
  stdout += `
26550
26677
  `;
26551
26678
  stdout += printSection("Recent Validation Signals");
26552
- if (existsSync5(path11.join(cwd, "package.json"))) {
26679
+ if (existsSync6(path12.join(cwd, "package.json"))) {
26553
26680
  stdout += `package.json detected. Common scripts:
26554
26681
  `;
26555
26682
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -26558,7 +26685,7 @@ ${status.stderr}` : "");
26558
26685
  `) + `
26559
26686
  `;
26560
26687
  }
26561
- if (existsSync5(path11.join(cwd, "Cargo.toml")) || existsSync5(path11.join(cwd, "src-tauri/Cargo.toml"))) {
26688
+ if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
26562
26689
  stdout += `Rust project detected.
26563
26690
  `;
26564
26691
  }
@@ -26591,7 +26718,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
26591
26718
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
26592
26719
  - If Unreleased already has duplicate category headings, normalize_only first.`;
26593
26720
  function changelogContext(root, range) {
26594
- const cwd = path11.resolve(repoRoot(root));
26721
+ const cwd = path12.resolve(repoRoot(root));
26595
26722
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26596
26723
  return invalidRange(cwd);
26597
26724
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26609,8 +26736,8 @@ function changelogContext(root, range) {
26609
26736
  stdout += CHANGELOG_RULES + `
26610
26737
  `;
26611
26738
  stdout += printSection("Existing CHANGELOG.md");
26612
- const changelogPath = path11.join(cwd, "CHANGELOG.md");
26613
- if (existsSync5(changelogPath)) {
26739
+ const changelogPath = path12.join(cwd, "CHANGELOG.md");
26740
+ if (existsSync6(changelogPath)) {
26614
26741
  stdout += readTrimmed(changelogPath, 260) + `
26615
26742
  `;
26616
26743
  } else {
@@ -26629,7 +26756,7 @@ function changelogContext(root, range) {
26629
26756
  return { stdout, stderr: "", exitCode: 0, cwd };
26630
26757
  }
26631
26758
  function docsRefreshContext(root, range) {
26632
- const cwd = path11.resolve(repoRoot(root));
26759
+ const cwd = path12.resolve(repoRoot(root));
26633
26760
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26634
26761
  return invalidRange(cwd);
26635
26762
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26651,8 +26778,8 @@ function docsRefreshContext(root, range) {
26651
26778
  `) + `
26652
26779
  `;
26653
26780
  stdout += printSection("README Preview");
26654
- const readme = path11.join(cwd, "README.md");
26655
- if (existsSync5(readme)) {
26781
+ const readme = path12.join(cwd, "README.md");
26782
+ if (existsSync6(readme)) {
26656
26783
  stdout += readTrimmed(readme, 220) + `
26657
26784
  `;
26658
26785
  } else {
@@ -26660,8 +26787,8 @@ function docsRefreshContext(root, range) {
26660
26787
  `;
26661
26788
  }
26662
26789
  stdout += printSection("Package Scripts");
26663
- const pkgPath = path11.join(cwd, "package.json");
26664
- if (existsSync5(pkgPath)) {
26790
+ const pkgPath = path12.join(cwd, "package.json");
26791
+ if (existsSync6(pkgPath)) {
26665
26792
  try {
26666
26793
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
26667
26794
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -26674,7 +26801,7 @@ function docsRefreshContext(root, range) {
26674
26801
  return { stdout, stderr: "", exitCode: 0, cwd };
26675
26802
  }
26676
26803
  function releaseNotesContext(root, rangeOrTag) {
26677
- const cwd = path11.resolve(repoRoot(root));
26804
+ const cwd = path12.resolve(repoRoot(root));
26678
26805
  if (!rangeOrTag) {
26679
26806
  return { stdout: "", stderr: `ERROR: release tag or range required
26680
26807
  `, exitCode: 1, cwd };
@@ -26712,7 +26839,7 @@ function releaseNotesContext(root, rangeOrTag) {
26712
26839
  `;
26713
26840
  stdout += printSection("Existing Release Files");
26714
26841
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
26715
- if (existsSync5(path11.join(cwd, rel)))
26842
+ if (existsSync6(path12.join(cwd, rel)))
26716
26843
  stdout += rel + `
26717
26844
  `;
26718
26845
  }
@@ -26903,25 +27030,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
26903
27030
  import fs10 from "node:fs";
26904
27031
  import path13 from "node:path";
26905
27032
 
26906
- // packages/workit-core/src/core/package-root.ts
26907
- import { existsSync as existsSync6 } from "node:fs";
26908
- import path12 from "node:path";
26909
- import { fileURLToPath } from "node:url";
26910
- var packageRoot = () => {
26911
- let dir = path12.dirname(fileURLToPath(import.meta.url));
26912
- while (true) {
26913
- const parent = path12.dirname(dir);
26914
- if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
26915
- return dir;
26916
- dir = parent;
26917
- }
26918
- };
26919
- var assetRoot = () => {
26920
- const root = packageRoot();
26921
- const assets = path12.join(root, "assets");
26922
- return existsSync6(assets) ? assets : root;
26923
- };
26924
-
26925
27033
  // packages/workit-core/src/core/templates.ts
26926
27034
  var repoRoot2 = assetRoot();
26927
27035
 
@@ -27425,6 +27533,32 @@ var toolInputSchema = (family) => {
27425
27533
  const schema = boundedOperationJsonSchema(family);
27426
27534
  return { type: "object", ...schema };
27427
27535
  };
27536
+ var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
27537
+ var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
27538
+ const action = branch.properties?.action?.const;
27539
+ return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
27540
+ });
27541
+ var advertisedToolSchemas = (attested) => {
27542
+ if (attested)
27543
+ return OPERATION_FAMILIES.map((family) => ({
27544
+ name: `workit_${family}`,
27545
+ description: operationDescription(family),
27546
+ inputSchema: toolInputSchema(family)
27547
+ }));
27548
+ return OPERATION_FAMILIES.flatMap((family) => {
27549
+ const branches = readOnlyBranches(family);
27550
+ if (!branches.length)
27551
+ return [];
27552
+ const schema = boundedOperationJsonSchema(family);
27553
+ return [
27554
+ {
27555
+ name: `workit_${family}`,
27556
+ description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
27557
+ inputSchema: { type: "object", ...schema, oneOf: branches }
27558
+ }
27559
+ ];
27560
+ });
27561
+ };
27428
27562
  var sanitizeFailure = (result, workspaceRoot) => {
27429
27563
  if (result.ok)
27430
27564
  return result;
@@ -27479,13 +27613,16 @@ function createMcpServer(host, contextProvider) {
27479
27613
  ].filter((file) => existsSync7(file)));
27480
27614
  let staleWarned = false;
27481
27615
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
27482
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
27483
- tools: OPERATION_FAMILIES.map((family) => ({
27484
- name: `workit_${family}`,
27485
- description: operationDescription(family),
27486
- inputSchema: toolInputSchema(family)
27487
- }))
27488
- }));
27616
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
27617
+ let attested = false;
27618
+ try {
27619
+ const context = await contextProvider.current();
27620
+ attested = context.callerAttested === true;
27621
+ } catch {
27622
+ attested = false;
27623
+ }
27624
+ return { tools: advertisedToolSchemas(attested) };
27625
+ });
27489
27626
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
27490
27627
  resources: CONTEXT_KINDS.map((kind) => ({
27491
27628
  uri: `workit://context/${kind}`,
@@ -27600,7 +27737,7 @@ function createMcpServer(host, contextProvider) {
27600
27737
  const parsed = parseOperation(family, request.params.arguments);
27601
27738
  if (!parsed.ok)
27602
27739
  return resultForClient(parsed, workspaceRoot);
27603
- if (context.callerAttested === false && requiresCallerIdentity(parsed.data))
27740
+ if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
27604
27741
  return resultForClient({
27605
27742
  ok: false,
27606
27743
  schemaVersion: 1,
@@ -27660,6 +27797,13 @@ var codexCapabilities = (host, availability = {}) => {
27660
27797
  reason: "file writes are host-policy; PreToolUse allows write tools",
27661
27798
  refs: [ref(host, "PreToolUse")]
27662
27799
  },
27800
+ {
27801
+ name: "fresh-context-review",
27802
+ surface: "SubagentStart",
27803
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
27804
+ 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",
27805
+ refs: [ref(host, "SubagentStart")]
27806
+ },
27663
27807
  {
27664
27808
  name: "native_subagents",
27665
27809
  surface: "SubagentStart/SubagentStop",