@brainervirus/workit-cursor 1.0.8 → 1.0.10

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.
@@ -12479,8 +12479,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
12479
12479
 
12480
12480
  // packages/workit-core/src/core.ts
12481
12481
  import { spawnSync as spawnSync2 } from "node:child_process";
12482
- import { existsSync as existsSync3, realpathSync as realpathSync5 } from "node:fs";
12483
- import path6 from "node:path";
12482
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
12483
+ import path7 from "node:path";
12484
12484
 
12485
12485
  // packages/workit-core/src/core/task-contract.ts
12486
12486
  import { createHash, randomUUID } from "node:crypto";
@@ -20272,6 +20272,7 @@ var decisionSchema = object4({
20272
20272
  scope: scopeSchema,
20273
20273
  presented: text,
20274
20274
  approvedContent: text,
20275
+ displayed: text.optional(),
20275
20276
  contentRefs: array3(refSchema)
20276
20277
  }).strict(),
20277
20278
  digest,
@@ -20310,7 +20311,7 @@ var workerReportSchema = object4({
20310
20311
  }).strict();
20311
20312
  var workerSchema = object4({
20312
20313
  assignment: assignmentSchema,
20313
- state: _enum2(["assigned", "running", "cancelling", "stopped", "unknown"]),
20314
+ state: _enum2(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
20314
20315
  session: refSchema.nullable(),
20315
20316
  report: workerReportSchema.nullable()
20316
20317
  }).strict();
@@ -20350,6 +20351,10 @@ var actionProgressListSchema = array3(actionProgressSchema).check((ctx) => {
20350
20351
  path: ["decisionId"]
20351
20352
  });
20352
20353
  });
20354
+ var runtimeSchema = object4({
20355
+ createdWith: text.nullable(),
20356
+ updatedWith: text
20357
+ }).strict();
20353
20358
  var taskRecordSchema = object4({
20354
20359
  schemaVersion: literal3(1),
20355
20360
  id,
@@ -20363,6 +20368,8 @@ var taskRecordSchema = object4({
20363
20368
  status: _enum2(["active", "paused", "closed"]),
20364
20369
  closure: closureSchema.nullable(),
20365
20370
  progress: progressSchema,
20371
+ pauseReason: text.nullable().optional(),
20372
+ runtime: runtimeSchema.optional(),
20366
20373
  assessments: array3(entrySchema(assessmentSchema)),
20367
20374
  policy: policySchema.nullable(),
20368
20375
  policyChanges: array3(policyChangeSchema),
@@ -20378,6 +20385,7 @@ var workspaceRecordSchema = object4({
20378
20385
  id,
20379
20386
  revision,
20380
20387
  root: nonEmpty,
20388
+ runtime: runtimeSchema.optional(),
20381
20389
  writer: object4({ state: _enum2(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
20382
20390
  }).strict();
20383
20391
  var capabilitySchema = object4({
@@ -20405,6 +20413,9 @@ var taskSummarySchema = object4({
20405
20413
  id,
20406
20414
  revision,
20407
20415
  workspaceRevision: revision,
20416
+ createdAt: utc,
20417
+ updatedAt: utc,
20418
+ runtime: runtimeSchema.nullable(),
20408
20419
  objective: text,
20409
20420
  status: _enum2(["active", "paused", "closed"]),
20410
20421
  closure: closureSchema.nullable(),
@@ -20475,7 +20486,7 @@ var taskOperations = {
20475
20486
  expectedWorkspaceRevision: revision.optional(),
20476
20487
  outcome: outcomeSchema,
20477
20488
  summary: text,
20478
- decisionIds: array3(id)
20489
+ decisionIds: array3(id).optional()
20479
20490
  })
20480
20491
  };
20481
20492
  var policyOperations = {
@@ -20567,14 +20578,15 @@ var writerOperations = {
20567
20578
  ...taskId,
20568
20579
  expectedRevision: revision.optional(),
20569
20580
  expectedWorkspaceRevision: revision.optional(),
20570
- workerId: nullableId.optional()
20581
+ workerId: nullableId.optional(),
20582
+ reason: text.optional()
20571
20583
  }),
20572
20584
  release: operation({
20573
20585
  action: literal3("release"),
20574
20586
  ...taskId,
20575
20587
  expectedRevision: revision.optional(),
20576
20588
  expectedWorkspaceRevision: revision.optional(),
20577
- reason: text
20589
+ reason: text.optional()
20578
20590
  })
20579
20591
  };
20580
20592
  var stateOperations = {
@@ -20781,7 +20793,26 @@ function requirementId(input) {
20781
20793
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
20782
20794
  import * as fs4 from "node:fs";
20783
20795
  import { hostname as hostname2 } from "node:os";
20784
- import path3 from "node:path";
20796
+ import path4 from "node:path";
20797
+
20798
+ // packages/workit-core/src/core/package-root.ts
20799
+ import { existsSync } from "node:fs";
20800
+ import path from "node:path";
20801
+ import { fileURLToPath } from "node:url";
20802
+ var packageRoot = () => {
20803
+ let dir = path.dirname(fileURLToPath(import.meta.url));
20804
+ while (true) {
20805
+ const parent = path.dirname(dir);
20806
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
20807
+ return dir;
20808
+ dir = parent;
20809
+ }
20810
+ };
20811
+ var assetRoot = () => {
20812
+ const root = packageRoot();
20813
+ const assets = path.join(root, "assets");
20814
+ return existsSync(assets) ? assets : root;
20815
+ };
20785
20816
 
20786
20817
  // node_modules/@openclaw/fs-safe/dist/errors.js
20787
20818
  var OPERATIONAL_CODES = new Set([
@@ -20832,7 +20863,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
20832
20863
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
20833
20864
  import { randomBytes } from "node:crypto";
20834
20865
  import fsSync from "node:fs";
20835
- import path from "node:path";
20866
+ import path2 from "node:path";
20836
20867
 
20837
20868
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
20838
20869
  import fs from "node:fs";
@@ -20922,13 +20953,13 @@ ${ownershipToken}
20922
20953
  return { raw, ownershipToken };
20923
20954
  }
20924
20955
  function relativeSidecarLockPath(lockRoot, lockPath) {
20925
- const resolved = path.resolve(lockPath);
20926
- const lexicalRelative = path.relative(lockRoot.rootDir, resolved);
20927
- const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path.sep}`) && !path.isAbsolute(lexicalRelative) ? lexicalRelative : path.relative(lockRoot.rootReal, resolved);
20928
- if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
20956
+ const resolved = path2.resolve(lockPath);
20957
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
20958
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
20959
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
20929
20960
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
20930
20961
  }
20931
- return relative.split(path.sep).join(path.posix.sep);
20962
+ return relative.split(path2.sep).join(path2.posix.sep);
20932
20963
  }
20933
20964
  function parseSidecarLockPayload(raw, parser) {
20934
20965
  if (parser) {
@@ -21075,7 +21106,7 @@ function getFsSafeLockConfig() {
21075
21106
 
21076
21107
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
21077
21108
  import fs3 from "node:fs";
21078
- import path2 from "node:path";
21109
+ import path3 from "node:path";
21079
21110
 
21080
21111
  // node_modules/@openclaw/fs-safe/dist/timing.js
21081
21112
  function sleepSync(ms) {
@@ -21166,26 +21197,26 @@ function canonicalLockParentSync(parent) {
21166
21197
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
21167
21198
  }
21168
21199
  function normalizeTargetPath(targetPath) {
21169
- const resolved = path2.resolve(targetPath);
21170
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
21200
+ const resolved = path3.resolve(targetPath);
21201
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
21171
21202
  try {
21172
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
21203
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
21173
21204
  } catch {
21174
21205
  return resolved;
21175
21206
  }
21176
21207
  }
21177
21208
  function boundedLockPath(lockPath, lockRoot) {
21178
- const resolved = path2.resolve(lockPath);
21209
+ const resolved = path3.resolve(lockPath);
21179
21210
  if (!lockRoot)
21180
21211
  return resolved;
21181
21212
  relativeSidecarLockPath(lockRoot, resolved);
21182
- const parent = path2.dirname(resolved);
21213
+ const parent = path3.dirname(resolved);
21183
21214
  const parentReal = canonicalLockParentSync(parent);
21184
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
21185
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
21215
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
21216
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
21186
21217
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
21187
21218
  }
21188
- return path2.join(parentReal, path2.basename(resolved));
21219
+ return path3.join(parentReal, path3.basename(resolved));
21189
21220
  }
21190
21221
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
21191
21222
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -21398,6 +21429,33 @@ var metadataLockSchema = object4({
21398
21429
  nonce: string5().min(1)
21399
21430
  }).strict();
21400
21431
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21432
+ var cachedRuntimeVersion = null;
21433
+ var runtimeVersion = () => {
21434
+ if (cachedRuntimeVersion === null) {
21435
+ try {
21436
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
21437
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
21438
+ } catch {
21439
+ cachedRuntimeVersion = "0.0.0";
21440
+ }
21441
+ }
21442
+ return cachedRuntimeVersion;
21443
+ };
21444
+ var stampNew = (record) => ({
21445
+ createdWith: record.runtime?.createdWith ?? null,
21446
+ updatedWith: runtimeVersion()
21447
+ });
21448
+ var isNewerVersion = (candidate, current) => {
21449
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
21450
+ const [next, now] = [parts(candidate), parts(current)];
21451
+ for (let index = 0;index < 3; index += 1) {
21452
+ const left = next[index] ?? 0;
21453
+ const right = now[index] ?? 0;
21454
+ if (left !== right)
21455
+ return left > right;
21456
+ }
21457
+ return false;
21458
+ };
21401
21459
  var jsonBytes = (value) => `${canonicalJson(value)}
21402
21460
  `;
21403
21461
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -21424,12 +21482,12 @@ var sameMetadataLock = (left, right) => {
21424
21482
  class TaskStore {
21425
21483
  root;
21426
21484
  constructor(root) {
21427
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
21485
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
21428
21486
  }
21429
21487
  readTask(taskId) {
21430
21488
  if (!validId(taskId))
21431
21489
  return failure("invalid_input", "task ID is invalid", { taskId });
21432
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
21490
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
21433
21491
  if (!result.exists)
21434
21492
  return failure("not_found", "task not found", { taskId });
21435
21493
  if (result.result.ok && result.result.data.id !== taskId)
@@ -21463,7 +21521,7 @@ class TaskStore {
21463
21521
  });
21464
21522
  const tasks = [];
21465
21523
  for (const name of names.sort()) {
21466
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
21524
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
21467
21525
  if (!item.exists)
21468
21526
  continue;
21469
21527
  if (!item.result.ok)
@@ -21497,7 +21555,7 @@ class TaskStore {
21497
21555
  if (!current.ok)
21498
21556
  return current;
21499
21557
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
21500
- return failure("revision_conflict", "workspace revision does not match", {
21558
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21501
21559
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
21502
21560
  actualWorkspaceRevision: current.data?.revision ?? null
21503
21561
  });
@@ -21509,11 +21567,17 @@ class TaskStore {
21509
21567
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
21510
21568
  if (current.data && !previousWorkspaceBytes)
21511
21569
  return failure("storage_error", "workspace snapshot disappeared during creation");
21512
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
21570
+ const workspace = current.data ? {
21571
+ ...current.data,
21572
+ revision: newRevision(),
21573
+ root: this.root,
21574
+ runtime: stampNew(current.data)
21575
+ } : {
21513
21576
  schemaVersion: SCHEMA_VERSION,
21514
21577
  id: newId(),
21515
21578
  revision: newRevision(),
21516
21579
  root: this.root,
21580
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21517
21581
  writer: null
21518
21582
  };
21519
21583
  const timestamp = value.now ?? now();
@@ -21535,6 +21599,7 @@ class TaskStore {
21535
21599
  status: "active",
21536
21600
  closure: null,
21537
21601
  progress: { summary: "", nextAction: null, blockers: [] },
21602
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21538
21603
  assessments: [],
21539
21604
  policy: null,
21540
21605
  policyChanges: [],
@@ -21565,7 +21630,7 @@ class TaskStore {
21565
21630
  if (!current.ok)
21566
21631
  return current;
21567
21632
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
21568
- return failure("revision_conflict", "workspace revision does not match", {
21633
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21569
21634
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
21570
21635
  actualWorkspaceRevision: current.data?.revision ?? null
21571
21636
  });
@@ -21573,11 +21638,17 @@ class TaskStore {
21573
21638
  if (current.data && !previousWorkspaceBytes)
21574
21639
  return failure("storage_error", "workspace snapshot disappeared during import");
21575
21640
  const timestamp = input.now ?? now();
21576
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
21641
+ const workspace = current.data ? {
21642
+ ...current.data,
21643
+ revision: newRevision(),
21644
+ root: this.root,
21645
+ runtime: stampNew(current.data)
21646
+ } : {
21577
21647
  schemaVersion: SCHEMA_VERSION,
21578
21648
  id: input.workspaceId ?? newId(),
21579
21649
  revision: newRevision(),
21580
21650
  root: this.root,
21651
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21581
21652
  writer: null
21582
21653
  };
21583
21654
  const task = {
@@ -21585,7 +21656,11 @@ class TaskStore {
21585
21656
  workspaceId: workspace.id,
21586
21657
  revision: newRevision(),
21587
21658
  createdAt: timestamp,
21588
- updatedAt: timestamp
21659
+ updatedAt: timestamp,
21660
+ runtime: {
21661
+ createdWith: input.task.runtime?.createdWith ?? null,
21662
+ updatedWith: runtimeVersion()
21663
+ }
21589
21664
  };
21590
21665
  const validTask = taskRecordSchema.safeParse(task);
21591
21666
  if (!validTask.success)
@@ -21634,7 +21709,8 @@ class TaskStore {
21634
21709
  workspaceId: current.data.workspaceId,
21635
21710
  createdAt: current.data.createdAt,
21636
21711
  revision: context.revision,
21637
- updatedAt: context.now
21712
+ updatedAt: context.now,
21713
+ runtime: stampNew(current.data)
21638
21714
  };
21639
21715
  const valid = taskRecordSchema.safeParse(record);
21640
21716
  if (!valid.success)
@@ -21668,7 +21744,8 @@ class TaskStore {
21668
21744
  ...changed.data,
21669
21745
  id: current.data.id,
21670
21746
  root: this.root,
21671
- revision: context.revision
21747
+ revision: context.revision,
21748
+ runtime: stampNew(current.data)
21672
21749
  };
21673
21750
  const valid = workspaceRecordSchema.safeParse(record);
21674
21751
  if (!valid.success)
@@ -21711,7 +21788,8 @@ class TaskStore {
21711
21788
  ...changedWorkspace.data,
21712
21789
  id: workspace.data.id,
21713
21790
  root: this.root,
21714
- revision: workspaceContext.revision
21791
+ revision: workspaceContext.revision,
21792
+ runtime: stampNew(workspace.data)
21715
21793
  });
21716
21794
  if (!nextWorkspace.success)
21717
21795
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -21736,7 +21814,8 @@ class TaskStore {
21736
21814
  workspaceId: task.data.workspaceId,
21737
21815
  createdAt: task.data.createdAt,
21738
21816
  revision: taskContext.revision,
21739
- updatedAt: taskContext.now
21817
+ updatedAt: taskContext.now,
21818
+ runtime: stampNew(task.data)
21740
21819
  });
21741
21820
  if (!nextTask.success) {
21742
21821
  this.markUncertain(nextWorkspace.data);
@@ -21763,7 +21842,7 @@ class TaskStore {
21763
21842
  if (match)
21764
21843
  candidates.push({
21765
21844
  target: match[1],
21766
- path: path3.join(this.recoveryDir, name),
21845
+ path: path4.join(this.recoveryDir, name),
21767
21846
  digest: match[3]
21768
21847
  });
21769
21848
  }
@@ -21956,7 +22035,8 @@ class TaskStore {
21956
22035
  const value = {
21957
22036
  ...workspace,
21958
22037
  writer: { ...workspace.writer, state: "uncertain" },
21959
- revision: newRevision()
22038
+ revision: newRevision(),
22039
+ runtime: stampNew(workspace)
21960
22040
  };
21961
22041
  this.replaceSnapshot(this.workspacePath, value, workspace);
21962
22042
  }
@@ -22034,7 +22114,7 @@ class TaskStore {
22034
22114
  }
22035
22115
  fs4.renameSync(temporary, file);
22036
22116
  temporary = undefined;
22037
- this.fsyncDirectory(path3.dirname(file));
22117
+ this.fsyncDirectory(path4.dirname(file));
22038
22118
  return success(null, null, value);
22039
22119
  } catch (error) {
22040
22120
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -22048,9 +22128,9 @@ class TaskStore {
22048
22128
  }
22049
22129
  }
22050
22130
  saveRecovery(file, bytes) {
22051
- const target = path3.basename(file) === "workspace.json" ? "workspace" : "task";
22052
- const id = target === "task" ? path3.basename(file, ".json") : "workspace";
22053
- const destination = path3.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
22131
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
22132
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
22133
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
22054
22134
  if (fs4.existsSync(destination)) {
22055
22135
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
22056
22136
  return;
@@ -22128,7 +22208,7 @@ class TaskStore {
22128
22208
  for (const name of fs4.readdirSync(this.tasksDir)) {
22129
22209
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
22130
22210
  return null;
22131
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
22211
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
22132
22212
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
22133
22213
  return null;
22134
22214
  ids.add(parsed.data.workspaceId);
@@ -22170,7 +22250,12 @@ class TaskStore {
22170
22250
  if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
22171
22251
  return failure("unsupported_version", "unsupported snapshot schema version");
22172
22252
  const parsed = schema.safeParse(value);
22173
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
22253
+ if (parsed.success)
22254
+ return success(null, null, parsed.data);
22255
+ const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
22256
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
22257
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
22258
+ return failure("recovery_required", "snapshot does not satisfy its schema");
22174
22259
  }
22175
22260
  processStart(pid) {
22176
22261
  try {
@@ -22180,43 +22265,98 @@ class TaskStore {
22180
22265
  }
22181
22266
  }
22182
22267
  conflict(expected, actual) {
22183
- return failure("revision_conflict", "snapshot revision does not match", {
22268
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
22184
22269
  expectedRevision: expected,
22185
22270
  actualRevision: actual
22186
22271
  });
22187
22272
  }
22188
22273
  taskPath(taskId) {
22189
- return path3.join(this.tasksDir, `${taskId}.json`);
22274
+ return path4.join(this.tasksDir, `${taskId}.json`);
22190
22275
  }
22191
22276
  get workitDir() {
22192
- return path3.join(this.root, ".workit");
22277
+ return path4.join(this.root, ".workit");
22193
22278
  }
22194
22279
  get tasksDir() {
22195
- return path3.join(this.workitDir, "tasks");
22280
+ return path4.join(this.workitDir, "tasks");
22196
22281
  }
22197
22282
  get recoveryDir() {
22198
- return path3.join(this.workitDir, "recovery");
22283
+ return path4.join(this.workitDir, "recovery");
22199
22284
  }
22200
22285
  get workspacePath() {
22201
- return path3.join(this.workitDir, "workspace.json");
22286
+ return path4.join(this.workitDir, "workspace.json");
22202
22287
  }
22203
22288
  get lockPath() {
22204
- return path3.join(this.workitDir, "metadata.lock");
22289
+ return path4.join(this.workitDir, "metadata.lock");
22205
22290
  }
22206
22291
  get gitignorePath() {
22207
- return path3.join(this.workitDir, ".gitignore");
22292
+ return path4.join(this.workitDir, ".gitignore");
22208
22293
  }
22209
22294
  }
22295
+ // packages/workit-core/src/core/workers.ts
22296
+ var same = (left, right) => {
22297
+ try {
22298
+ return canonicalJson(left) === canonicalJson(right);
22299
+ } catch {
22300
+ return false;
22301
+ }
22302
+ };
22303
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
22304
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
22305
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
22306
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
22307
+ const code = char.charCodeAt(0);
22308
+ return code < 32 || code === 127;
22309
+ }) && !value.split(/[\\/]/).includes("..");
22310
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
22311
+ var workerIdOf = (value) => value.workerId ?? null;
22312
+ function assertProductWriteAllowed(input) {
22313
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
22314
+ return failure("invalid_input", "invalid product write path");
22315
+ if (!input.store)
22316
+ return failure("permission_denied", "write authorization requires core state");
22317
+ const task = input.store.readTask(input.task.id);
22318
+ if (!task.ok)
22319
+ return task;
22320
+ const workspace = input.store.readWorkspace();
22321
+ if (!workspace.ok)
22322
+ return workspace;
22323
+ if (!workspace.data)
22324
+ return failure("not_found", "workspace not found");
22325
+ const workspaceRecord = workspace.data;
22326
+ if (task.data.status !== "active")
22327
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
22328
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
22329
+ return failure("recovery_required", "task and workspace bindings are invalid");
22330
+ const writer = workspaceRecord.writer;
22331
+ if (!writer)
22332
+ return failure("permission_denied", "checkout has no writer owner");
22333
+ if (writer.state === "uncertain")
22334
+ return failure("recovery_required", "checkout writer requires recovery", {
22335
+ owner: writer.owner
22336
+ });
22337
+ const caller = callerValue(input.caller);
22338
+ const workerId = workerIdOf(input.caller);
22339
+ const owner = writer.owner;
22340
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
22341
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
22342
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
22343
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
22344
+ return failure("permission_denied", "only implementers can own product writes");
22345
+ 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))))
22346
+ return failure("recovery_required", "worker session is not an active writer");
22347
+ return success(null, workspaceRecord.revision, owner);
22348
+ }
22349
+
22210
22350
  // packages/workit-core/src/core/task-evaluation.ts
22211
22351
  import { createHash as createHash4 } from "node:crypto";
22212
22352
  import * as fs6 from "node:fs";
22213
22353
  import { spawnSync } from "node:child_process";
22214
- import path5 from "node:path";
22354
+ import path6 from "node:path";
22215
22355
 
22216
22356
  // packages/workit-core/src/core/authority.ts
22217
22357
  import { createHash as createHash3 } from "node:crypto";
22218
22358
  import * as fs5 from "node:fs";
22219
- import path4 from "node:path";
22359
+ import path5 from "node:path";
22220
22360
  var verifiedAuthorities = new WeakMap;
22221
22361
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
22222
22362
  const token = {};
@@ -22343,20 +22483,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
22343
22483
  continue;
22344
22484
  if (!reference.digest)
22345
22485
  return failure("invalid_input", "document references require a byte digest");
22346
- const target = path4.resolve(root, reference.path);
22347
- if (target !== root && !target.startsWith(`${root}${path4.sep}`))
22348
- return failure("invalid_input", "document reference escapes checkout");
22486
+ const target = path5.resolve(root, reference.path);
22487
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
22488
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22489
+ fields: [
22490
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
22491
+ ]
22492
+ });
22349
22493
  try {
22350
- const relative = path4.relative(root, target);
22494
+ const relative = path5.relative(root, target);
22351
22495
  let current = root;
22352
- for (const segment of relative.split(path4.sep)) {
22496
+ for (const segment of relative.split(path5.sep)) {
22353
22497
  if (!segment)
22354
22498
  continue;
22355
- current = path4.join(current, segment);
22499
+ current = path5.join(current, segment);
22356
22500
  const stat = fs5.lstatSync(current);
22357
22501
  const real = fs5.realpathSync(current);
22358
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
22359
- return failure("invalid_input", "document reference escapes checkout");
22502
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
22503
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22504
+ fields: [
22505
+ {
22506
+ path: "contentRefs.path",
22507
+ reason: `${reference.path} resolves outside the checkout`
22508
+ }
22509
+ ]
22510
+ });
22360
22511
  if (stat.isSymbolicLink())
22361
22512
  return failure("invalid_input", "document reference uses a symlink");
22362
22513
  if (current === target && !stat.isFile())
@@ -22703,14 +22854,14 @@ var verifyDecisionContent = verifyContentRefs;
22703
22854
 
22704
22855
  // packages/workit-core/src/core/task-evaluation.ts
22705
22856
  var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
22706
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path5.sep}`);
22707
- var relative = (root, candidate) => path5.relative(root, candidate).split(path5.sep).join("/") || ".";
22857
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
22858
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
22708
22859
  var canonicalPath = (value) => {
22709
22860
  if (typeof value !== "string" || !value)
22710
22861
  return null;
22711
22862
  if (value.includes("\\") || value.split("/").includes(".."))
22712
22863
  return null;
22713
- const normalized = path5.posix.normalize(value);
22864
+ const normalized = path6.posix.normalize(value);
22714
22865
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
22715
22866
  return null;
22716
22867
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -22758,14 +22909,14 @@ var gitPaths = (root) => {
22758
22909
  git: /not a git repository/i.test(stderr) ? false : true
22759
22910
  };
22760
22911
  }
22761
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
22912
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
22762
22913
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
22763
22914
  if (stagedDeleted.status !== 0) {
22764
22915
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
22765
22916
  if (!/not a git repository/i.test(stderr))
22766
22917
  return { paths, uncertain: true, git: true };
22767
22918
  } else if (stagedDeleted.stdout) {
22768
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
22919
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
22769
22920
  }
22770
22921
  return { paths: [...new Set(paths)], uncertain: false, git: true };
22771
22922
  };
@@ -22780,7 +22931,7 @@ var walk = (root, directory, output) => {
22780
22931
  for (const name of names) {
22781
22932
  if (name === ".git" || name === ".workit")
22782
22933
  continue;
22783
- const target = path5.join(directory, name);
22934
+ const target = path6.join(directory, name);
22784
22935
  let stat;
22785
22936
  try {
22786
22937
  stat = fs6.lstatSync(target);
@@ -22798,17 +22949,17 @@ var walk = (root, directory, output) => {
22798
22949
  var scopeRoots = (root, scope) => {
22799
22950
  const roots = [];
22800
22951
  for (const item of scope.paths.length ? scope.paths : ["."]) {
22801
- const target = path5.resolve(root, item);
22952
+ const target = path6.resolve(root, item);
22802
22953
  if (!inside(root, target))
22803
- return failure("invalid_input", "candidate scope escapes checkout");
22954
+ 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` }] });
22804
22955
  let ancestor = target;
22805
22956
  while (!fs6.existsSync(ancestor) && ancestor !== root)
22806
- ancestor = path5.dirname(ancestor);
22957
+ ancestor = path6.dirname(ancestor);
22807
22958
  try {
22808
22959
  if (!inside(root, fs6.realpathSync(ancestor)))
22809
- return failure("invalid_input", "candidate scope escapes checkout");
22960
+ 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` }] });
22810
22961
  } catch {
22811
- return failure("invalid_input", "candidate scope cannot be inspected");
22962
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
22812
22963
  }
22813
22964
  roots.push(target);
22814
22965
  }
@@ -22823,7 +22974,7 @@ function captureCandidate(root, scope, environment = []) {
22823
22974
  }
22824
22975
  const normalizedScope = canonicalScope(scope);
22825
22976
  if (!normalizedScope)
22826
- return failure("invalid_input", "candidate scope is invalid");
22977
+ 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" }] });
22827
22978
  const roots = scopeRoots(checkout, normalizedScope);
22828
22979
  if (!roots.ok)
22829
22980
  return roots;
@@ -22855,7 +23006,7 @@ function captureCandidate(root, scope, environment = []) {
22855
23006
  for (const item of [...names].sort()) {
22856
23007
  if (!scopeMatches(item, normalizedScope))
22857
23008
  continue;
22858
- const target = path5.join(checkout, item);
23009
+ const target = path6.join(checkout, item);
22859
23010
  try {
22860
23011
  const stat = fs6.lstatSync(target);
22861
23012
  if (stat.isSymbolicLink()) {
@@ -23012,7 +23163,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
23012
23163
  return true;
23013
23164
  const reviewSession = sessionFromRef(entry.data.reviewContext);
23014
23165
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
23015
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
23166
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
23016
23167
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
23017
23168
  });
23018
23169
  if (passed.length) {
@@ -23108,7 +23259,17 @@ function evaluateClosure(requestedOutcome, view) {
23108
23259
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
23109
23260
  if (requestedOutcome !== "stopped" && blocking.length)
23110
23261
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
23111
- requirementIds: blocking.map((item) => item.requirementId)
23262
+ requirementIds: blocking.map((item) => item.requirementId),
23263
+ requirements: blocking.map((item) => {
23264
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
23265
+ return {
23266
+ requirementId: item.requirementId,
23267
+ ruleId: requirement?.ruleId ?? "unknown",
23268
+ reason: item.reason,
23269
+ satisfaction: requirement?.satisfaction ?? "",
23270
+ dependentAction: requirement?.dependentAction ?? null
23271
+ };
23272
+ })
23112
23273
  });
23113
23274
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
23114
23275
  if (requestedOutcome === "verified" && accepted.length)
@@ -23144,7 +23305,7 @@ function reconcileResume(view, observations = []) {
23144
23305
  return candidate;
23145
23306
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
23146
23307
  const blockers = [...view.task.progress.blockers];
23147
- if (view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
23308
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
23148
23309
  blockers.push({
23149
23310
  reason: "worker state requires reconciliation",
23150
23311
  dependentAction: "resume",
@@ -23177,7 +23338,11 @@ var externalActionSchema = discriminatedUnion2("operation", [
23177
23338
  }).strict(),
23178
23339
  object4({
23179
23340
  operation: literal3("git.commit"),
23180
- payload: object4({ message: string5().min(1) }).strict()
23341
+ payload: object4({
23342
+ message: string5().min(1).optional(),
23343
+ plan_steps: array3(string5().min(1)).optional(),
23344
+ plan_branch: string5().min(1).optional()
23345
+ }).strict()
23181
23346
  }).strict(),
23182
23347
  object4({
23183
23348
  operation: literal3("git.push"),
@@ -23412,7 +23577,7 @@ function resolveRequirements(input) {
23412
23577
  ].join(", ")}`,
23413
23578
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
23414
23579
  before: "dependent_action",
23415
- dependentAction: "dependent-action",
23580
+ dependentAction: null,
23416
23581
  acceptanceAllowed: false
23417
23582
  }));
23418
23583
  if (signals.productChoiceOpen.value === true)
@@ -23423,7 +23588,7 @@ function resolveRequirements(input) {
23423
23588
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
23424
23589
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
23425
23590
  before: "dependent_action",
23426
- dependentAction: "dependent-action",
23591
+ dependentAction: null,
23427
23592
  acceptanceAllowed: false
23428
23593
  }));
23429
23594
  if (signals.behaviorChange.value === true)
@@ -23581,59 +23746,6 @@ function diffPolicy(previous, next, reason, now) {
23581
23746
  return parsed.data;
23582
23747
  }
23583
23748
 
23584
- // packages/workit-core/src/core/workers.ts
23585
- var same = (left, right) => {
23586
- try {
23587
- return canonicalJson(left) === canonicalJson(right);
23588
- } catch {
23589
- return false;
23590
- }
23591
- };
23592
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
23593
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
23594
- const code = char.charCodeAt(0);
23595
- return code < 32 || code === 127;
23596
- }) && !value.split(/[\\/]/).includes("..");
23597
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
23598
- var workerIdOf = (value) => value.workerId ?? null;
23599
- function assertProductWriteAllowed(input) {
23600
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
23601
- return failure("invalid_input", "invalid product write path");
23602
- if (!input.store)
23603
- return failure("permission_denied", "write authorization requires core state");
23604
- const task = input.store.readTask(input.task.id);
23605
- if (!task.ok)
23606
- return task;
23607
- const workspace = input.store.readWorkspace();
23608
- if (!workspace.ok)
23609
- return workspace;
23610
- if (!workspace.data)
23611
- return failure("not_found", "workspace not found");
23612
- const workspaceRecord = workspace.data;
23613
- if (task.data.status !== "active")
23614
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
23615
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
23616
- return failure("recovery_required", "task and workspace bindings are invalid");
23617
- const writer = workspaceRecord.writer;
23618
- if (!writer)
23619
- return failure("permission_denied", "checkout has no writer owner");
23620
- if (writer.state === "uncertain")
23621
- return failure("recovery_required", "checkout writer requires recovery", {
23622
- owner: writer.owner
23623
- });
23624
- const caller = callerValue(input.caller);
23625
- const workerId = workerIdOf(input.caller);
23626
- const owner = writer.owner;
23627
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
23628
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
23629
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
23630
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
23631
- return failure("permission_denied", "only implementers can own product writes");
23632
- 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))))
23633
- return failure("recovery_required", "worker session is not an active writer");
23634
- return success(null, workspaceRecord.revision, owner);
23635
- }
23636
-
23637
23749
  // packages/workit-core/src/core/task-engine.ts
23638
23750
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
23639
23751
  kind,
@@ -23752,10 +23864,10 @@ var applyWorkerLifecycle = (input) => {
23752
23864
  const notStarted = input.dispatch === true;
23753
23865
  if (notStarted && (input.state !== "stopped" || input.session !== null))
23754
23866
  return failure("invalid_input", "a never-dispatched worker stops with no session");
23755
- if (notStarted && (entry.data.state === "stopped" || entry.data.state === "unknown"))
23756
- return failure("invalid_transition", "worker is no longer awaiting dispatch");
23757
- if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
23867
+ if (notStarted && entry.data.session !== null)
23758
23868
  return failure("invalid_transition", "worker already has an observed session");
23869
+ if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
23870
+ return failure("invalid_transition", "worker launch was not claimed");
23759
23871
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
23760
23872
  return failure("invalid_input", "running worker observations require a session");
23761
23873
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -23764,15 +23876,20 @@ var applyWorkerLifecycle = (input) => {
23764
23876
  return failure("invalid_transition", "worker has not started");
23765
23877
  if (input.state === "running" && entry.data.state === "stopped")
23766
23878
  return failure("invalid_transition", "stopped worker cannot run again");
23879
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
23767
23880
  const nextEntry = {
23768
23881
  ...entry,
23769
23882
  recordedAt: input.now ?? entry.recordedAt,
23770
23883
  provenance: authority.provenance,
23771
- data: { ...entry.data, state: input.state, session: input.session }
23884
+ data: {
23885
+ ...entry.data,
23886
+ state: terminalCancel ? "cancelling" : input.state,
23887
+ session: input.session
23888
+ }
23772
23889
  };
23773
23890
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
23774
23891
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
23775
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
23892
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
23776
23893
  return success(task.data.revision, workspace.data.revision, entry);
23777
23894
  const changed = input.store.mutateTaskAndWorkspace({
23778
23895
  taskId: task.data.id,
@@ -24142,7 +24259,7 @@ class WorkitCore {
24142
24259
  activeWorkerBlocker(task, workspace) {
24143
24260
  if (workspace.writer)
24144
24261
  return failure("recovery_required", "writer ownership must be released first");
24145
- if (task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
24262
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
24146
24263
  return failure("recovery_required", "worker state requires reconciliation");
24147
24264
  return null;
24148
24265
  }
@@ -24379,11 +24496,6 @@ class WorkitCore {
24379
24496
  if (helper && !helper.ok)
24380
24497
  return helper;
24381
24498
  const evidence = input.evidence;
24382
- if (helper?.ok) {
24383
- const assignment = helper.data.data.assignment;
24384
- 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))
24385
- return failure("permission_denied", "evidence is outside the worker assignment");
24386
- }
24387
24499
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
24388
24500
  return failure("invalid_input", "missing or skipped evidence requires a reason");
24389
24501
  if (evidence.kind === "review") {
@@ -24400,6 +24512,11 @@ class WorkitCore {
24400
24512
  evidence.beforeCandidateId ??= null;
24401
24513
  evidence.candidateId ??= null;
24402
24514
  }
24515
+ if (helper?.ok) {
24516
+ const assignment = helper.data.data.assignment;
24517
+ 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))
24518
+ return failure("permission_denied", "evidence is outside the worker assignment");
24519
+ }
24403
24520
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
24404
24521
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
24405
24522
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -24906,7 +25023,12 @@ class WorkitCore {
24906
25023
  return failure("invalid_transition", "worker is not awaiting dispatch");
24907
25024
  return success(mutation.revision, null, {
24908
25025
  ...current,
24909
- workers: current.workers.map((item) => item.id === input.workerId ? { ...item, recordedAt: mutation.now, provenance: attested.data } : item)
25026
+ workers: current.workers.map((item) => item.id === input.workerId ? {
25027
+ ...item,
25028
+ recordedAt: mutation.now,
25029
+ provenance: attested.data,
25030
+ data: { ...item.data, state: "dispatching" }
25031
+ } : item)
24910
25032
  });
24911
25033
  }
24912
25034
  });
@@ -25290,6 +25412,9 @@ class WorkitCore {
25290
25412
  id: task.id,
25291
25413
  revision: task.revision,
25292
25414
  workspaceRevision: workspace.data.revision,
25415
+ createdAt: task.createdAt,
25416
+ updatedAt: task.updatedAt,
25417
+ runtime: task.runtime ?? null,
25293
25418
  objective: task.intent.data.objective,
25294
25419
  status: task.status,
25295
25420
  closure: task.closure,
@@ -25375,7 +25500,7 @@ class WorkitCore {
25375
25500
  const base = reconcileResume(effectiveView);
25376
25501
  if (!base.ok)
25377
25502
  return base;
25378
- const needsReconciliation = view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
25503
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
25379
25504
  const blockers = [...base.data.blockers];
25380
25505
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
25381
25506
  blockers.push({
@@ -25442,7 +25567,8 @@ class WorkitCore {
25442
25567
  ...current,
25443
25568
  status,
25444
25569
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
25445
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
25570
+ progress: current.progress,
25571
+ pauseReason: status === "paused" ? input.reason ?? null : null
25446
25572
  })
25447
25573
  });
25448
25574
  if (!changed.ok)
@@ -25478,7 +25604,7 @@ class WorkitCore {
25478
25604
  return workspace;
25479
25605
  if (!workspace.data)
25480
25606
  return failure("not_found", "workspace not found");
25481
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
25607
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
25482
25608
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
25483
25609
  this.fillRevisions(input, task.data, workspace.data);
25484
25610
  const changed = this.store.mutateTaskAndWorkspace({
@@ -25589,15 +25715,15 @@ function resolveGitRevision(root, value) {
25589
25715
  }
25590
25716
  function resolveInside(root, candidate) {
25591
25717
  const base = realpathSync5(root);
25592
- const target = path6.resolve(base, candidate);
25593
- if (target !== base && !target.startsWith(base + path6.sep)) {
25718
+ const target = path7.resolve(base, candidate);
25719
+ if (target !== base && !target.startsWith(base + path7.sep)) {
25594
25720
  throw new Error("path must stay inside repository root");
25595
25721
  }
25596
25722
  let ancestor = target;
25597
- while (!existsSync3(ancestor))
25598
- ancestor = path6.dirname(ancestor);
25723
+ while (!existsSync4(ancestor))
25724
+ ancestor = path7.dirname(ancestor);
25599
25725
  const canonicalAncestor = realpathSync5(ancestor);
25600
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path6.sep)) {
25726
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
25601
25727
  throw new Error("path must stay inside repository root");
25602
25728
  }
25603
25729
  return target;
@@ -25782,14 +25908,14 @@ var gitContext = (workspaceRoot, paths = []) => {
25782
25908
  import {
25783
25909
  copyFileSync,
25784
25910
  cpSync,
25785
- existsSync as existsSync4,
25911
+ existsSync as existsSync5,
25786
25912
  mkdirSync as mkdirSync2,
25787
25913
  readFileSync as readFileSync4,
25788
25914
  readdirSync as readdirSync3,
25789
25915
  writeFileSync as writeFileSync2
25790
25916
  } from "node:fs";
25791
25917
  import os2 from "node:os";
25792
- import path7 from "node:path";
25918
+ import path8 from "node:path";
25793
25919
  var diagnosticLogger;
25794
25920
  var PRESETS = {
25795
25921
  gitflow: {
@@ -25810,7 +25936,7 @@ var mergePreset = (preset, input = {}, current = {
25810
25936
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
25811
25937
  };
25812
25938
  };
25813
- 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");
25939
+ 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");
25814
25940
  var migratedDir = null;
25815
25941
  var migrationFailed = false;
25816
25942
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -25820,12 +25946,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25820
25946
  migratedDir = dir;
25821
25947
  return dir;
25822
25948
  }
25823
- const legacy = path7.join(process.env.XDG_CONFIG_HOME || path7.join(os2.homedir(), ".config"), "workflow-toolkit");
25824
- if (!existsSync4(legacy)) {
25949
+ const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
25950
+ if (!existsSync5(legacy)) {
25825
25951
  migratedDir = dir;
25826
25952
  return dir;
25827
25953
  }
25828
- if (!migrationFailed && existsSync4(dir)) {
25954
+ if (!migrationFailed && existsSync5(dir)) {
25829
25955
  migratedDir = dir;
25830
25956
  return dir;
25831
25957
  }
@@ -25833,9 +25959,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25833
25959
  mkdirSync2(dir, { recursive: true });
25834
25960
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
25835
25961
  for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
25836
- const src = path7.join(legacy, entry.name);
25837
- const dest = path7.join(dir, entry.name);
25838
- if (existsSync4(dest))
25962
+ const src = path8.join(legacy, entry.name);
25963
+ const dest = path8.join(dir, entry.name);
25964
+ if (existsSync5(dest))
25839
25965
  continue;
25840
25966
  try {
25841
25967
  if (entry.isDirectory())
@@ -25908,7 +26034,7 @@ var parseConfigResult = (raw, file) => {
25908
26034
  };
25909
26035
  };
25910
26036
  var readConfigTyped = (dir) => {
25911
- const file = path7.join(dir ?? configDir(), "config.json");
26037
+ const file = path8.join(dir ?? configDir(), "config.json");
25912
26038
  return parseConfigResult(readSafe(file), file);
25913
26039
  };
25914
26040
  var readConfig = () => {
@@ -25944,9 +26070,9 @@ var COMMIT_PRESETS = [
25944
26070
 
25945
26071
  // packages/workit-core/src/core/workspaces.ts
25946
26072
  import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
25947
- import path8 from "node:path";
26073
+ import path9 from "node:path";
25948
26074
  var readWorkspacesResult = (dir = configDir()) => {
25949
- const file = path8.join(dir, "workspaces.json");
26075
+ const file = path9.join(dir, "workspaces.json");
25950
26076
  let raw;
25951
26077
  try {
25952
26078
  raw = readFileSync5(file, "utf8");
@@ -26044,10 +26170,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
26044
26170
 
26045
26171
  // packages/workit-core/src/core/vcs-config.ts
26046
26172
  import fs7 from "node:fs";
26047
- import path9 from "node:path";
26173
+ import path10 from "node:path";
26048
26174
  import { spawnSync as spawnSync3 } from "node:child_process";
26049
26175
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
26050
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
26176
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
26051
26177
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
26052
26178
  var remoteProvider = (cwd) => {
26053
26179
  const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
@@ -26130,8 +26256,8 @@ function vcsConfig(mode, cwd) {
26130
26256
  }
26131
26257
  const prov = cfg[provider] ?? {};
26132
26258
  const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
26133
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path9.join(configDir(), `${provider}.token`));
26134
- const tokenPath = path9.resolve(tokenFile);
26259
+ const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
26260
+ const tokenPath = path10.resolve(tokenFile);
26135
26261
  let tokenOk = false;
26136
26262
  if (fs7.existsSync(tokenPath)) {
26137
26263
  const token = fs7.readFileSync(tokenPath, "utf8").trim();
@@ -26140,7 +26266,7 @@ function vcsConfig(mode, cwd) {
26140
26266
  }
26141
26267
  const out = {
26142
26268
  ok: true,
26143
- configPath: path9.resolve(cfgPath),
26269
+ configPath: path10.resolve(cfgPath),
26144
26270
  provider,
26145
26271
  defaultTargetBranch: defaultTarget,
26146
26272
  pr: cfg.pr ?? {},
@@ -26233,18 +26359,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
26233
26359
 
26234
26360
  // packages/workit-core/src/core/pr-create.ts
26235
26361
  import fs8 from "node:fs";
26236
- import path10 from "node:path";
26362
+ import path11 from "node:path";
26237
26363
  function parseGhIssue(value) {
26238
26364
  const m = /issues\/(\d+)/.exec(value);
26239
26365
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26240
26366
  }
26241
26367
  function whichOnPath(tool) {
26242
26368
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26243
- for (const dir of (process.env.PATH ?? "").split(path10.delimiter)) {
26369
+ for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
26244
26370
  if (!dir)
26245
26371
  continue;
26246
26372
  for (const name of names) {
26247
- const candidate = path10.join(dir, name);
26373
+ const candidate = path11.join(dir, name);
26248
26374
  try {
26249
26375
  fs8.accessSync(candidate, fs8.constants.X_OK);
26250
26376
  return candidate;
@@ -26257,8 +26383,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
26257
26383
 
26258
26384
  // packages/workit-core/src/core/repo-context.ts
26259
26385
  import { spawnSync as spawnSync4 } from "node:child_process";
26260
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26261
- import path11 from "node:path";
26386
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26387
+ import path12 from "node:path";
26262
26388
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26263
26389
  try {
26264
26390
  gitRevisionParts(value);
@@ -26392,7 +26518,7 @@ var findPrTemplate = (cwd) => {
26392
26518
  ]) {
26393
26519
  const parts = rel.split("/");
26394
26520
  const base = parts[parts.length - 1];
26395
- const dir = parts.length > 1 ? path11.join(cwd, ...parts.slice(0, -1)) : cwd;
26521
+ const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
26396
26522
  const actual = probeCaseInsensitive(dir, base);
26397
26523
  if (actual)
26398
26524
  return [...parts.slice(0, -1), actual].join("/");
@@ -26423,8 +26549,8 @@ var diffStatForRange = (cwd, range) => {
26423
26549
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26424
26550
  };
26425
26551
  var packageScripts = (cwd, keys) => {
26426
- const pkgPath = path11.join(cwd, "package.json");
26427
- if (!existsSync5(pkgPath))
26552
+ const pkgPath = path12.join(cwd, "package.json");
26553
+ if (!existsSync6(pkgPath))
26428
26554
  return [];
26429
26555
  try {
26430
26556
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
@@ -26449,9 +26575,9 @@ var documentationFiles = (cwd) => {
26449
26575
  if (entry.isDirectory()) {
26450
26576
  if (excluded.has(entry.name))
26451
26577
  continue;
26452
- walk(path11.join(dir, entry.name), depth + 1);
26578
+ walk(path12.join(dir, entry.name), depth + 1);
26453
26579
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26454
- matches.push("./" + path11.relative(cwd, path11.join(dir, entry.name)).replaceAll("\\", "/"));
26580
+ matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
26455
26581
  }
26456
26582
  }
26457
26583
  };
@@ -26459,7 +26585,7 @@ var documentationFiles = (cwd) => {
26459
26585
  return matches.sort().slice(0, 200);
26460
26586
  };
26461
26587
  var readTrimmed = (file, maxLines) => {
26462
- if (!existsSync5(file))
26588
+ if (!existsSync6(file))
26463
26589
  return "";
26464
26590
  const lines = readFileSync6(file, "utf8").split(`
26465
26591
  `);
@@ -26467,7 +26593,7 @@ var readTrimmed = (file, maxLines) => {
26467
26593
  `);
26468
26594
  };
26469
26595
  function prReadyContext(root, range) {
26470
- const cwd = path11.resolve(repoRoot(root));
26596
+ const cwd = path12.resolve(repoRoot(root));
26471
26597
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26472
26598
  return invalidRange(cwd);
26473
26599
  let stdout = "";
@@ -26525,7 +26651,7 @@ ${status.stderr}` : "");
26525
26651
  stdout += `template_path: ${template}
26526
26652
 
26527
26653
  `;
26528
- stdout += readTrimmed(path11.join(cwd, template), 220);
26654
+ stdout += readTrimmed(path12.join(cwd, template), 220);
26529
26655
  } else {
26530
26656
  stdout += `template_path: none
26531
26657
 
@@ -26535,7 +26661,7 @@ ${status.stderr}` : "");
26535
26661
  stdout += `
26536
26662
  `;
26537
26663
  stdout += printSection("Recent Validation Signals");
26538
- if (existsSync5(path11.join(cwd, "package.json"))) {
26664
+ if (existsSync6(path12.join(cwd, "package.json"))) {
26539
26665
  stdout += `package.json detected. Common scripts:
26540
26666
  `;
26541
26667
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -26544,7 +26670,7 @@ ${status.stderr}` : "");
26544
26670
  `) + `
26545
26671
  `;
26546
26672
  }
26547
- if (existsSync5(path11.join(cwd, "Cargo.toml")) || existsSync5(path11.join(cwd, "src-tauri/Cargo.toml"))) {
26673
+ if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
26548
26674
  stdout += `Rust project detected.
26549
26675
  `;
26550
26676
  }
@@ -26577,7 +26703,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
26577
26703
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
26578
26704
  - If Unreleased already has duplicate category headings, normalize_only first.`;
26579
26705
  function changelogContext(root, range) {
26580
- const cwd = path11.resolve(repoRoot(root));
26706
+ const cwd = path12.resolve(repoRoot(root));
26581
26707
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26582
26708
  return invalidRange(cwd);
26583
26709
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26595,8 +26721,8 @@ function changelogContext(root, range) {
26595
26721
  stdout += CHANGELOG_RULES + `
26596
26722
  `;
26597
26723
  stdout += printSection("Existing CHANGELOG.md");
26598
- const changelogPath = path11.join(cwd, "CHANGELOG.md");
26599
- if (existsSync5(changelogPath)) {
26724
+ const changelogPath = path12.join(cwd, "CHANGELOG.md");
26725
+ if (existsSync6(changelogPath)) {
26600
26726
  stdout += readTrimmed(changelogPath, 260) + `
26601
26727
  `;
26602
26728
  } else {
@@ -26615,7 +26741,7 @@ function changelogContext(root, range) {
26615
26741
  return { stdout, stderr: "", exitCode: 0, cwd };
26616
26742
  }
26617
26743
  function docsRefreshContext(root, range) {
26618
- const cwd = path11.resolve(repoRoot(root));
26744
+ const cwd = path12.resolve(repoRoot(root));
26619
26745
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26620
26746
  return invalidRange(cwd);
26621
26747
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26637,8 +26763,8 @@ function docsRefreshContext(root, range) {
26637
26763
  `) + `
26638
26764
  `;
26639
26765
  stdout += printSection("README Preview");
26640
- const readme = path11.join(cwd, "README.md");
26641
- if (existsSync5(readme)) {
26766
+ const readme = path12.join(cwd, "README.md");
26767
+ if (existsSync6(readme)) {
26642
26768
  stdout += readTrimmed(readme, 220) + `
26643
26769
  `;
26644
26770
  } else {
@@ -26646,8 +26772,8 @@ function docsRefreshContext(root, range) {
26646
26772
  `;
26647
26773
  }
26648
26774
  stdout += printSection("Package Scripts");
26649
- const pkgPath = path11.join(cwd, "package.json");
26650
- if (existsSync5(pkgPath)) {
26775
+ const pkgPath = path12.join(cwd, "package.json");
26776
+ if (existsSync6(pkgPath)) {
26651
26777
  try {
26652
26778
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
26653
26779
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -26660,7 +26786,7 @@ function docsRefreshContext(root, range) {
26660
26786
  return { stdout, stderr: "", exitCode: 0, cwd };
26661
26787
  }
26662
26788
  function releaseNotesContext(root, rangeOrTag) {
26663
- const cwd = path11.resolve(repoRoot(root));
26789
+ const cwd = path12.resolve(repoRoot(root));
26664
26790
  if (!rangeOrTag) {
26665
26791
  return { stdout: "", stderr: `ERROR: release tag or range required
26666
26792
  `, exitCode: 1, cwd };
@@ -26698,7 +26824,7 @@ function releaseNotesContext(root, rangeOrTag) {
26698
26824
  `;
26699
26825
  stdout += printSection("Existing Release Files");
26700
26826
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
26701
- if (existsSync5(path11.join(cwd, rel)))
26827
+ if (existsSync6(path12.join(cwd, rel)))
26702
26828
  stdout += rel + `
26703
26829
  `;
26704
26830
  }
@@ -26889,25 +27015,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
26889
27015
  import fs10 from "node:fs";
26890
27016
  import path13 from "node:path";
26891
27017
 
26892
- // packages/workit-core/src/core/package-root.ts
26893
- import { existsSync as existsSync6 } from "node:fs";
26894
- import path12 from "node:path";
26895
- import { fileURLToPath } from "node:url";
26896
- var packageRoot = () => {
26897
- let dir = path12.dirname(fileURLToPath(import.meta.url));
26898
- while (true) {
26899
- const parent = path12.dirname(dir);
26900
- if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
26901
- return dir;
26902
- dir = parent;
26903
- }
26904
- };
26905
- var assetRoot = () => {
26906
- const root = packageRoot();
26907
- const assets = path12.join(root, "assets");
26908
- return existsSync6(assets) ? assets : root;
26909
- };
26910
-
26911
27018
  // packages/workit-core/src/core/templates.ts
26912
27019
  var repoRoot2 = assetRoot();
26913
27020
 
@@ -27411,6 +27518,32 @@ var toolInputSchema = (family) => {
27411
27518
  const schema = boundedOperationJsonSchema(family);
27412
27519
  return { type: "object", ...schema };
27413
27520
  };
27521
+ var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
27522
+ var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
27523
+ const action = branch.properties?.action?.const;
27524
+ return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
27525
+ });
27526
+ var advertisedToolSchemas = (attested) => {
27527
+ if (attested)
27528
+ return OPERATION_FAMILIES.map((family) => ({
27529
+ name: `workit_${family}`,
27530
+ description: operationDescription(family),
27531
+ inputSchema: toolInputSchema(family)
27532
+ }));
27533
+ return OPERATION_FAMILIES.flatMap((family) => {
27534
+ const branches = readOnlyBranches(family);
27535
+ if (!branches.length)
27536
+ return [];
27537
+ const schema = boundedOperationJsonSchema(family);
27538
+ return [
27539
+ {
27540
+ name: `workit_${family}`,
27541
+ description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
27542
+ inputSchema: { type: "object", ...schema, oneOf: branches }
27543
+ }
27544
+ ];
27545
+ });
27546
+ };
27414
27547
  var sanitizeFailure = (result, workspaceRoot) => {
27415
27548
  if (result.ok)
27416
27549
  return result;
@@ -27465,13 +27598,16 @@ function createMcpServer(host, contextProvider) {
27465
27598
  ].filter((file) => existsSync7(file)));
27466
27599
  let staleWarned = false;
27467
27600
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
27468
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
27469
- tools: OPERATION_FAMILIES.map((family) => ({
27470
- name: `workit_${family}`,
27471
- description: operationDescription(family),
27472
- inputSchema: toolInputSchema(family)
27473
- }))
27474
- }));
27601
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
27602
+ let attested = false;
27603
+ try {
27604
+ const context = await contextProvider.current();
27605
+ attested = context.callerAttested === true;
27606
+ } catch {
27607
+ attested = false;
27608
+ }
27609
+ return { tools: advertisedToolSchemas(attested) };
27610
+ });
27475
27611
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
27476
27612
  resources: CONTEXT_KINDS.map((kind) => ({
27477
27613
  uri: `workit://context/${kind}`,
@@ -27586,7 +27722,7 @@ function createMcpServer(host, contextProvider) {
27586
27722
  const parsed = parseOperation(family, request.params.arguments);
27587
27723
  if (!parsed.ok)
27588
27724
  return resultForClient(parsed, workspaceRoot);
27589
- if (context.callerAttested === false && requiresCallerIdentity(parsed.data))
27725
+ if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
27590
27726
  return resultForClient({
27591
27727
  ok: false,
27592
27728
  schemaVersion: 1,
@@ -27675,6 +27811,13 @@ var cursorCapabilities = (availability = {}) => {
27675
27811
  reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
27676
27812
  refs: [hostRef("subagentStart")]
27677
27813
  },
27814
+ {
27815
+ name: "fresh-context-review",
27816
+ surface: "subagentStart",
27817
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
27818
+ reason: has("subagentStart") ? "independent review runs as a bounded reviewer subagent; stops lack stable identity, so reviewer exclusivity is evaluated from recorded evidence" : "Cursor subagentStart is unavailable for independent review",
27819
+ refs: [hostRef("subagentStart")]
27820
+ },
27678
27821
  {
27679
27822
  name: "arbitrary_shell_write",
27680
27823
  surface: "unobservable_shell",