@brainervirus/workit-cursor 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.
@@ -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,10 +20272,12 @@ var decisionSchema = object4({
20272
20272
  scope: scopeSchema,
20273
20273
  presented: text,
20274
20274
  approvedContent: text,
20275
- contentRefs: array3(refSchema)
20275
+ displayed: text.optional(),
20276
+ contentRefs: array3(refSchema),
20277
+ statedChoice: object4({ ref: text, text }).strict().optional()
20276
20278
  }).strict(),
20277
20279
  digest,
20278
- response: _enum2(["approved", "rejected"]),
20280
+ response: _enum2(["approved", "rejected", "stated"]),
20279
20281
  requirementIds: array3(digest),
20280
20282
  revoked: object4({ at: utc, reason: text }).strict().nullable(),
20281
20283
  consumption: object4({
@@ -20350,6 +20352,10 @@ var actionProgressListSchema = array3(actionProgressSchema).check((ctx) => {
20350
20352
  path: ["decisionId"]
20351
20353
  });
20352
20354
  });
20355
+ var runtimeSchema = object4({
20356
+ createdWith: text.nullable(),
20357
+ updatedWith: text
20358
+ }).strict();
20353
20359
  var taskRecordSchema = object4({
20354
20360
  schemaVersion: literal3(1),
20355
20361
  id,
@@ -20363,6 +20369,8 @@ var taskRecordSchema = object4({
20363
20369
  status: _enum2(["active", "paused", "closed"]),
20364
20370
  closure: closureSchema.nullable(),
20365
20371
  progress: progressSchema,
20372
+ pauseReason: text.nullable().optional(),
20373
+ runtime: runtimeSchema.optional(),
20366
20374
  assessments: array3(entrySchema(assessmentSchema)),
20367
20375
  policy: policySchema.nullable(),
20368
20376
  policyChanges: array3(policyChangeSchema),
@@ -20378,6 +20386,7 @@ var workspaceRecordSchema = object4({
20378
20386
  id,
20379
20387
  revision,
20380
20388
  root: nonEmpty,
20389
+ runtime: runtimeSchema.optional(),
20381
20390
  writer: object4({ state: _enum2(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
20382
20391
  }).strict();
20383
20392
  var capabilitySchema = object4({
@@ -20405,6 +20414,9 @@ var taskSummarySchema = object4({
20405
20414
  id,
20406
20415
  revision,
20407
20416
  workspaceRevision: revision,
20417
+ createdAt: utc,
20418
+ updatedAt: utc,
20419
+ runtime: runtimeSchema.nullable(),
20408
20420
  objective: text,
20409
20421
  status: _enum2(["active", "paused", "closed"]),
20410
20422
  closure: closureSchema.nullable(),
@@ -20475,7 +20487,7 @@ var taskOperations = {
20475
20487
  expectedWorkspaceRevision: revision.optional(),
20476
20488
  outcome: outcomeSchema,
20477
20489
  summary: text,
20478
- decisionIds: array3(id)
20490
+ decisionIds: array3(id).optional()
20479
20491
  })
20480
20492
  };
20481
20493
  var policyOperations = {
@@ -20567,14 +20579,15 @@ var writerOperations = {
20567
20579
  ...taskId,
20568
20580
  expectedRevision: revision.optional(),
20569
20581
  expectedWorkspaceRevision: revision.optional(),
20570
- workerId: nullableId.optional()
20582
+ workerId: nullableId.optional(),
20583
+ reason: text.optional()
20571
20584
  }),
20572
20585
  release: operation({
20573
20586
  action: literal3("release"),
20574
20587
  ...taskId,
20575
20588
  expectedRevision: revision.optional(),
20576
20589
  expectedWorkspaceRevision: revision.optional(),
20577
- reason: text
20590
+ reason: text.optional()
20578
20591
  })
20579
20592
  };
20580
20593
  var stateOperations = {
@@ -20781,7 +20794,26 @@ function requirementId(input) {
20781
20794
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
20782
20795
  import * as fs4 from "node:fs";
20783
20796
  import { hostname as hostname2 } from "node:os";
20784
- import path3 from "node:path";
20797
+ import path4 from "node:path";
20798
+
20799
+ // packages/workit-core/src/core/package-root.ts
20800
+ import { existsSync } from "node:fs";
20801
+ import path from "node:path";
20802
+ import { fileURLToPath } from "node:url";
20803
+ var packageRoot = () => {
20804
+ let dir = path.dirname(fileURLToPath(import.meta.url));
20805
+ while (true) {
20806
+ const parent = path.dirname(dir);
20807
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
20808
+ return dir;
20809
+ dir = parent;
20810
+ }
20811
+ };
20812
+ var assetRoot = () => {
20813
+ const root = packageRoot();
20814
+ const assets = path.join(root, "assets");
20815
+ return existsSync(assets) ? assets : root;
20816
+ };
20785
20817
 
20786
20818
  // node_modules/@openclaw/fs-safe/dist/errors.js
20787
20819
  var OPERATIONAL_CODES = new Set([
@@ -20832,7 +20864,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
20832
20864
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
20833
20865
  import { randomBytes } from "node:crypto";
20834
20866
  import fsSync from "node:fs";
20835
- import path from "node:path";
20867
+ import path2 from "node:path";
20836
20868
 
20837
20869
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
20838
20870
  import fs from "node:fs";
@@ -20922,13 +20954,13 @@ ${ownershipToken}
20922
20954
  return { raw, ownershipToken };
20923
20955
  }
20924
20956
  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)) {
20957
+ const resolved = path2.resolve(lockPath);
20958
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
20959
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
20960
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
20929
20961
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
20930
20962
  }
20931
- return relative.split(path.sep).join(path.posix.sep);
20963
+ return relative.split(path2.sep).join(path2.posix.sep);
20932
20964
  }
20933
20965
  function parseSidecarLockPayload(raw, parser) {
20934
20966
  if (parser) {
@@ -21075,7 +21107,7 @@ function getFsSafeLockConfig() {
21075
21107
 
21076
21108
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
21077
21109
  import fs3 from "node:fs";
21078
- import path2 from "node:path";
21110
+ import path3 from "node:path";
21079
21111
 
21080
21112
  // node_modules/@openclaw/fs-safe/dist/timing.js
21081
21113
  function sleepSync(ms) {
@@ -21166,26 +21198,26 @@ function canonicalLockParentSync(parent) {
21166
21198
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
21167
21199
  }
21168
21200
  function normalizeTargetPath(targetPath) {
21169
- const resolved = path2.resolve(targetPath);
21170
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
21201
+ const resolved = path3.resolve(targetPath);
21202
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
21171
21203
  try {
21172
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
21204
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
21173
21205
  } catch {
21174
21206
  return resolved;
21175
21207
  }
21176
21208
  }
21177
21209
  function boundedLockPath(lockPath, lockRoot) {
21178
- const resolved = path2.resolve(lockPath);
21210
+ const resolved = path3.resolve(lockPath);
21179
21211
  if (!lockRoot)
21180
21212
  return resolved;
21181
21213
  relativeSidecarLockPath(lockRoot, resolved);
21182
- const parent = path2.dirname(resolved);
21214
+ const parent = path3.dirname(resolved);
21183
21215
  const parentReal = canonicalLockParentSync(parent);
21184
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
21185
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
21216
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
21217
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
21186
21218
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
21187
21219
  }
21188
- return path2.join(parentReal, path2.basename(resolved));
21220
+ return path3.join(parentReal, path3.basename(resolved));
21189
21221
  }
21190
21222
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
21191
21223
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -21398,6 +21430,33 @@ var metadataLockSchema = object4({
21398
21430
  nonce: string5().min(1)
21399
21431
  }).strict();
21400
21432
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
21433
+ var cachedRuntimeVersion = null;
21434
+ var runtimeVersion = () => {
21435
+ if (cachedRuntimeVersion === null) {
21436
+ try {
21437
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
21438
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
21439
+ } catch {
21440
+ cachedRuntimeVersion = "0.0.0";
21441
+ }
21442
+ }
21443
+ return cachedRuntimeVersion;
21444
+ };
21445
+ var stampNew = (record) => ({
21446
+ createdWith: record.runtime?.createdWith ?? null,
21447
+ updatedWith: runtimeVersion()
21448
+ });
21449
+ var isNewerVersion = (candidate, current) => {
21450
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
21451
+ const [next, now] = [parts(candidate), parts(current)];
21452
+ for (let index = 0;index < 3; index += 1) {
21453
+ const left = next[index] ?? 0;
21454
+ const right = now[index] ?? 0;
21455
+ if (left !== right)
21456
+ return left > right;
21457
+ }
21458
+ return false;
21459
+ };
21401
21460
  var jsonBytes = (value) => `${canonicalJson(value)}
21402
21461
  `;
21403
21462
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -21424,12 +21483,12 @@ var sameMetadataLock = (left, right) => {
21424
21483
  class TaskStore {
21425
21484
  root;
21426
21485
  constructor(root) {
21427
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
21486
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
21428
21487
  }
21429
21488
  readTask(taskId) {
21430
21489
  if (!validId(taskId))
21431
21490
  return failure("invalid_input", "task ID is invalid", { taskId });
21432
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
21491
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
21433
21492
  if (!result.exists)
21434
21493
  return failure("not_found", "task not found", { taskId });
21435
21494
  if (result.result.ok && result.result.data.id !== taskId)
@@ -21463,7 +21522,7 @@ class TaskStore {
21463
21522
  });
21464
21523
  const tasks = [];
21465
21524
  for (const name of names.sort()) {
21466
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
21525
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
21467
21526
  if (!item.exists)
21468
21527
  continue;
21469
21528
  if (!item.result.ok)
@@ -21497,7 +21556,7 @@ class TaskStore {
21497
21556
  if (!current.ok)
21498
21557
  return current;
21499
21558
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
21500
- return failure("revision_conflict", "workspace revision does not match", {
21559
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21501
21560
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
21502
21561
  actualWorkspaceRevision: current.data?.revision ?? null
21503
21562
  });
@@ -21509,11 +21568,17 @@ class TaskStore {
21509
21568
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
21510
21569
  if (current.data && !previousWorkspaceBytes)
21511
21570
  return failure("storage_error", "workspace snapshot disappeared during creation");
21512
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
21571
+ const workspace = current.data ? {
21572
+ ...current.data,
21573
+ revision: newRevision(),
21574
+ root: this.root,
21575
+ runtime: stampNew(current.data)
21576
+ } : {
21513
21577
  schemaVersion: SCHEMA_VERSION,
21514
21578
  id: newId(),
21515
21579
  revision: newRevision(),
21516
21580
  root: this.root,
21581
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21517
21582
  writer: null
21518
21583
  };
21519
21584
  const timestamp = value.now ?? now();
@@ -21535,6 +21600,7 @@ class TaskStore {
21535
21600
  status: "active",
21536
21601
  closure: null,
21537
21602
  progress: { summary: "", nextAction: null, blockers: [] },
21603
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21538
21604
  assessments: [],
21539
21605
  policy: null,
21540
21606
  policyChanges: [],
@@ -21565,7 +21631,7 @@ class TaskStore {
21565
21631
  if (!current.ok)
21566
21632
  return current;
21567
21633
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
21568
- return failure("revision_conflict", "workspace revision does not match", {
21634
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
21569
21635
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
21570
21636
  actualWorkspaceRevision: current.data?.revision ?? null
21571
21637
  });
@@ -21573,11 +21639,17 @@ class TaskStore {
21573
21639
  if (current.data && !previousWorkspaceBytes)
21574
21640
  return failure("storage_error", "workspace snapshot disappeared during import");
21575
21641
  const timestamp = input.now ?? now();
21576
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
21642
+ const workspace = current.data ? {
21643
+ ...current.data,
21644
+ revision: newRevision(),
21645
+ root: this.root,
21646
+ runtime: stampNew(current.data)
21647
+ } : {
21577
21648
  schemaVersion: SCHEMA_VERSION,
21578
21649
  id: input.workspaceId ?? newId(),
21579
21650
  revision: newRevision(),
21580
21651
  root: this.root,
21652
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
21581
21653
  writer: null
21582
21654
  };
21583
21655
  const task = {
@@ -21585,7 +21657,11 @@ class TaskStore {
21585
21657
  workspaceId: workspace.id,
21586
21658
  revision: newRevision(),
21587
21659
  createdAt: timestamp,
21588
- updatedAt: timestamp
21660
+ updatedAt: timestamp,
21661
+ runtime: {
21662
+ createdWith: input.task.runtime?.createdWith ?? null,
21663
+ updatedWith: runtimeVersion()
21664
+ }
21589
21665
  };
21590
21666
  const validTask = taskRecordSchema.safeParse(task);
21591
21667
  if (!validTask.success)
@@ -21634,7 +21710,8 @@ class TaskStore {
21634
21710
  workspaceId: current.data.workspaceId,
21635
21711
  createdAt: current.data.createdAt,
21636
21712
  revision: context.revision,
21637
- updatedAt: context.now
21713
+ updatedAt: context.now,
21714
+ runtime: stampNew(current.data)
21638
21715
  };
21639
21716
  const valid = taskRecordSchema.safeParse(record);
21640
21717
  if (!valid.success)
@@ -21668,7 +21745,8 @@ class TaskStore {
21668
21745
  ...changed.data,
21669
21746
  id: current.data.id,
21670
21747
  root: this.root,
21671
- revision: context.revision
21748
+ revision: context.revision,
21749
+ runtime: stampNew(current.data)
21672
21750
  };
21673
21751
  const valid = workspaceRecordSchema.safeParse(record);
21674
21752
  if (!valid.success)
@@ -21711,7 +21789,8 @@ class TaskStore {
21711
21789
  ...changedWorkspace.data,
21712
21790
  id: workspace.data.id,
21713
21791
  root: this.root,
21714
- revision: workspaceContext.revision
21792
+ revision: workspaceContext.revision,
21793
+ runtime: stampNew(workspace.data)
21715
21794
  });
21716
21795
  if (!nextWorkspace.success)
21717
21796
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -21736,7 +21815,8 @@ class TaskStore {
21736
21815
  workspaceId: task.data.workspaceId,
21737
21816
  createdAt: task.data.createdAt,
21738
21817
  revision: taskContext.revision,
21739
- updatedAt: taskContext.now
21818
+ updatedAt: taskContext.now,
21819
+ runtime: stampNew(task.data)
21740
21820
  });
21741
21821
  if (!nextTask.success) {
21742
21822
  this.markUncertain(nextWorkspace.data);
@@ -21763,7 +21843,7 @@ class TaskStore {
21763
21843
  if (match)
21764
21844
  candidates.push({
21765
21845
  target: match[1],
21766
- path: path3.join(this.recoveryDir, name),
21846
+ path: path4.join(this.recoveryDir, name),
21767
21847
  digest: match[3]
21768
21848
  });
21769
21849
  }
@@ -21956,7 +22036,8 @@ class TaskStore {
21956
22036
  const value = {
21957
22037
  ...workspace,
21958
22038
  writer: { ...workspace.writer, state: "uncertain" },
21959
- revision: newRevision()
22039
+ revision: newRevision(),
22040
+ runtime: stampNew(workspace)
21960
22041
  };
21961
22042
  this.replaceSnapshot(this.workspacePath, value, workspace);
21962
22043
  }
@@ -22034,7 +22115,7 @@ class TaskStore {
22034
22115
  }
22035
22116
  fs4.renameSync(temporary, file);
22036
22117
  temporary = undefined;
22037
- this.fsyncDirectory(path3.dirname(file));
22118
+ this.fsyncDirectory(path4.dirname(file));
22038
22119
  return success(null, null, value);
22039
22120
  } catch (error) {
22040
22121
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -22048,9 +22129,9 @@ class TaskStore {
22048
22129
  }
22049
22130
  }
22050
22131
  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`);
22132
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
22133
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
22134
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
22054
22135
  if (fs4.existsSync(destination)) {
22055
22136
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
22056
22137
  return;
@@ -22128,7 +22209,7 @@ class TaskStore {
22128
22209
  for (const name of fs4.readdirSync(this.tasksDir)) {
22129
22210
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
22130
22211
  return null;
22131
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
22212
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
22132
22213
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
22133
22214
  return null;
22134
22215
  ids.add(parsed.data.workspaceId);
@@ -22170,7 +22251,12 @@ class TaskStore {
22170
22251
  if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
22171
22252
  return failure("unsupported_version", "unsupported snapshot schema version");
22172
22253
  const parsed = schema.safeParse(value);
22173
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
22254
+ if (parsed.success)
22255
+ return success(null, null, parsed.data);
22256
+ const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
22257
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
22258
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
22259
+ return failure("recovery_required", "snapshot does not satisfy its schema");
22174
22260
  }
22175
22261
  processStart(pid) {
22176
22262
  try {
@@ -22180,43 +22266,98 @@ class TaskStore {
22180
22266
  }
22181
22267
  }
22182
22268
  conflict(expected, actual) {
22183
- return failure("revision_conflict", "snapshot revision does not match", {
22269
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
22184
22270
  expectedRevision: expected,
22185
22271
  actualRevision: actual
22186
22272
  });
22187
22273
  }
22188
22274
  taskPath(taskId) {
22189
- return path3.join(this.tasksDir, `${taskId}.json`);
22275
+ return path4.join(this.tasksDir, `${taskId}.json`);
22190
22276
  }
22191
22277
  get workitDir() {
22192
- return path3.join(this.root, ".workit");
22278
+ return path4.join(this.root, ".workit");
22193
22279
  }
22194
22280
  get tasksDir() {
22195
- return path3.join(this.workitDir, "tasks");
22281
+ return path4.join(this.workitDir, "tasks");
22196
22282
  }
22197
22283
  get recoveryDir() {
22198
- return path3.join(this.workitDir, "recovery");
22284
+ return path4.join(this.workitDir, "recovery");
22199
22285
  }
22200
22286
  get workspacePath() {
22201
- return path3.join(this.workitDir, "workspace.json");
22287
+ return path4.join(this.workitDir, "workspace.json");
22202
22288
  }
22203
22289
  get lockPath() {
22204
- return path3.join(this.workitDir, "metadata.lock");
22290
+ return path4.join(this.workitDir, "metadata.lock");
22205
22291
  }
22206
22292
  get gitignorePath() {
22207
- return path3.join(this.workitDir, ".gitignore");
22293
+ return path4.join(this.workitDir, ".gitignore");
22208
22294
  }
22209
22295
  }
22296
+ // packages/workit-core/src/core/workers.ts
22297
+ var same = (left, right) => {
22298
+ try {
22299
+ return canonicalJson(left) === canonicalJson(right);
22300
+ } catch {
22301
+ return false;
22302
+ }
22303
+ };
22304
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
22305
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
22306
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
22307
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
22308
+ const code = char.charCodeAt(0);
22309
+ return code < 32 || code === 127;
22310
+ }) && !value.split(/[\\/]/).includes("..");
22311
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
22312
+ var workerIdOf = (value) => value.workerId ?? null;
22313
+ function assertProductWriteAllowed(input) {
22314
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
22315
+ return failure("invalid_input", "invalid product write path");
22316
+ if (!input.store)
22317
+ return failure("permission_denied", "write authorization requires core state");
22318
+ const task = input.store.readTask(input.task.id);
22319
+ if (!task.ok)
22320
+ return task;
22321
+ const workspace = input.store.readWorkspace();
22322
+ if (!workspace.ok)
22323
+ return workspace;
22324
+ if (!workspace.data)
22325
+ return failure("not_found", "workspace not found");
22326
+ const workspaceRecord = workspace.data;
22327
+ if (task.data.status !== "active")
22328
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
22329
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
22330
+ return failure("recovery_required", "task and workspace bindings are invalid");
22331
+ const writer = workspaceRecord.writer;
22332
+ if (!writer)
22333
+ return failure("permission_denied", "checkout has no writer owner");
22334
+ if (writer.state === "uncertain")
22335
+ return failure("recovery_required", "checkout writer requires recovery", {
22336
+ owner: writer.owner
22337
+ });
22338
+ const caller = callerValue(input.caller);
22339
+ const workerId = workerIdOf(input.caller);
22340
+ const owner = writer.owner;
22341
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
22342
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
22343
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
22344
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
22345
+ return failure("permission_denied", "only implementers can own product writes");
22346
+ 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))))
22347
+ return failure("recovery_required", "worker session is not an active writer");
22348
+ return success(null, workspaceRecord.revision, owner);
22349
+ }
22350
+
22210
22351
  // packages/workit-core/src/core/task-evaluation.ts
22211
22352
  import { createHash as createHash4 } from "node:crypto";
22212
22353
  import * as fs6 from "node:fs";
22213
22354
  import { spawnSync } from "node:child_process";
22214
- import path5 from "node:path";
22355
+ import path6 from "node:path";
22215
22356
 
22216
22357
  // packages/workit-core/src/core/authority.ts
22217
22358
  import { createHash as createHash3 } from "node:crypto";
22218
22359
  import * as fs5 from "node:fs";
22219
- import path4 from "node:path";
22360
+ import path5 from "node:path";
22220
22361
  var verifiedAuthorities = new WeakMap;
22221
22362
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
22222
22363
  const token = {};
@@ -22343,20 +22484,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
22343
22484
  continue;
22344
22485
  if (!reference.digest)
22345
22486
  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");
22487
+ const target = path5.resolve(root, reference.path);
22488
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
22489
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22490
+ fields: [
22491
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
22492
+ ]
22493
+ });
22349
22494
  try {
22350
- const relative = path4.relative(root, target);
22495
+ const relative = path5.relative(root, target);
22351
22496
  let current = root;
22352
- for (const segment of relative.split(path4.sep)) {
22497
+ for (const segment of relative.split(path5.sep)) {
22353
22498
  if (!segment)
22354
22499
  continue;
22355
- current = path4.join(current, segment);
22500
+ current = path5.join(current, segment);
22356
22501
  const stat = fs5.lstatSync(current);
22357
22502
  const real = fs5.realpathSync(current);
22358
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
22359
- return failure("invalid_input", "document reference escapes checkout");
22503
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
22504
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
22505
+ fields: [
22506
+ {
22507
+ path: "contentRefs.path",
22508
+ reason: `${reference.path} resolves outside the checkout`
22509
+ }
22510
+ ]
22511
+ });
22360
22512
  if (stat.isSymbolicLink())
22361
22513
  return failure("invalid_input", "document reference uses a symlink");
22362
22514
  if (current === target && !stat.isFile())
@@ -22703,14 +22855,14 @@ var verifyDecisionContent = verifyContentRefs;
22703
22855
 
22704
22856
  // packages/workit-core/src/core/task-evaluation.ts
22705
22857
  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("/") || ".";
22858
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
22859
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
22708
22860
  var canonicalPath = (value) => {
22709
22861
  if (typeof value !== "string" || !value)
22710
22862
  return null;
22711
22863
  if (value.includes("\\") || value.split("/").includes(".."))
22712
22864
  return null;
22713
- const normalized = path5.posix.normalize(value);
22865
+ const normalized = path6.posix.normalize(value);
22714
22866
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
22715
22867
  return null;
22716
22868
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -22758,14 +22910,14 @@ var gitPaths = (root) => {
22758
22910
  git: /not a git repository/i.test(stderr) ? false : true
22759
22911
  };
22760
22912
  }
22761
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
22913
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
22762
22914
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
22763
22915
  if (stagedDeleted.status !== 0) {
22764
22916
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
22765
22917
  if (!/not a git repository/i.test(stderr))
22766
22918
  return { paths, uncertain: true, git: true };
22767
22919
  } else if (stagedDeleted.stdout) {
22768
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
22920
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
22769
22921
  }
22770
22922
  return { paths: [...new Set(paths)], uncertain: false, git: true };
22771
22923
  };
@@ -22780,7 +22932,7 @@ var walk = (root, directory, output) => {
22780
22932
  for (const name of names) {
22781
22933
  if (name === ".git" || name === ".workit")
22782
22934
  continue;
22783
- const target = path5.join(directory, name);
22935
+ const target = path6.join(directory, name);
22784
22936
  let stat;
22785
22937
  try {
22786
22938
  stat = fs6.lstatSync(target);
@@ -22798,17 +22950,17 @@ var walk = (root, directory, output) => {
22798
22950
  var scopeRoots = (root, scope) => {
22799
22951
  const roots = [];
22800
22952
  for (const item of scope.paths.length ? scope.paths : ["."]) {
22801
- const target = path5.resolve(root, item);
22953
+ const target = path6.resolve(root, item);
22802
22954
  if (!inside(root, target))
22803
- return failure("invalid_input", "candidate scope escapes checkout");
22955
+ 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
22956
  let ancestor = target;
22805
22957
  while (!fs6.existsSync(ancestor) && ancestor !== root)
22806
- ancestor = path5.dirname(ancestor);
22958
+ ancestor = path6.dirname(ancestor);
22807
22959
  try {
22808
22960
  if (!inside(root, fs6.realpathSync(ancestor)))
22809
- return failure("invalid_input", "candidate scope escapes checkout");
22961
+ 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
22962
  } catch {
22811
- return failure("invalid_input", "candidate scope cannot be inspected");
22963
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
22812
22964
  }
22813
22965
  roots.push(target);
22814
22966
  }
@@ -22823,7 +22975,7 @@ function captureCandidate(root, scope, environment = []) {
22823
22975
  }
22824
22976
  const normalizedScope = canonicalScope(scope);
22825
22977
  if (!normalizedScope)
22826
- return failure("invalid_input", "candidate scope is invalid");
22978
+ 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
22979
  const roots = scopeRoots(checkout, normalizedScope);
22828
22980
  if (!roots.ok)
22829
22981
  return roots;
@@ -22855,7 +23007,7 @@ function captureCandidate(root, scope, environment = []) {
22855
23007
  for (const item of [...names].sort()) {
22856
23008
  if (!scopeMatches(item, normalizedScope))
22857
23009
  continue;
22858
- const target = path5.join(checkout, item);
23010
+ const target = path6.join(checkout, item);
22859
23011
  try {
22860
23012
  const stat = fs6.lstatSync(target);
22861
23013
  if (stat.isSymbolicLink()) {
@@ -22977,7 +23129,7 @@ function evaluateEvidence(task, candidate) {
22977
23129
  }
22978
23130
  var scopeCovers2 = scopeCovers;
22979
23131
  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")));
22980
- 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 }));
23132
+ 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 }));
22981
23133
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
22982
23134
  if (kind !== "passed")
22983
23135
  return false;
@@ -23012,7 +23164,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
23012
23164
  return true;
23013
23165
  const reviewSession = sessionFromRef(entry.data.reviewContext);
23014
23166
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
23015
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
23167
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
23016
23168
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
23017
23169
  });
23018
23170
  if (passed.length) {
@@ -23053,7 +23205,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
23053
23205
  status: "satisfied",
23054
23206
  evidenceIds: [],
23055
23207
  decisionIds: decisions.map((decision) => decision.id),
23056
- reason: "an applicable approved decision satisfies the requirement"
23208
+ reason: "an applicable approved or stated decision satisfies the requirement"
23057
23209
  };
23058
23210
  const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
23059
23211
  if (limitations.length)
@@ -23108,7 +23260,17 @@ function evaluateClosure(requestedOutcome, view) {
23108
23260
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
23109
23261
  if (requestedOutcome !== "stopped" && blocking.length)
23110
23262
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
23111
- requirementIds: blocking.map((item) => item.requirementId)
23263
+ requirementIds: blocking.map((item) => item.requirementId),
23264
+ requirements: blocking.map((item) => {
23265
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
23266
+ return {
23267
+ requirementId: item.requirementId,
23268
+ ruleId: requirement?.ruleId ?? "unknown",
23269
+ reason: item.reason,
23270
+ satisfaction: requirement?.satisfaction ?? "",
23271
+ dependentAction: requirement?.dependentAction ?? null
23272
+ };
23273
+ })
23112
23274
  });
23113
23275
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
23114
23276
  if (requestedOutcome === "verified" && accepted.length)
@@ -23144,7 +23306,7 @@ function reconcileResume(view, observations = []) {
23144
23306
  return candidate;
23145
23307
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
23146
23308
  const blockers = [...view.task.progress.blockers];
23147
- if (view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
23309
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
23148
23310
  blockers.push({
23149
23311
  reason: "worker state requires reconciliation",
23150
23312
  dependentAction: "resume",
@@ -23177,7 +23339,11 @@ var externalActionSchema = discriminatedUnion2("operation", [
23177
23339
  }).strict(),
23178
23340
  object4({
23179
23341
  operation: literal3("git.commit"),
23180
- payload: object4({ message: string5().min(1) }).strict()
23342
+ payload: object4({
23343
+ message: string5().min(1).optional(),
23344
+ plan_steps: array3(string5().min(1)).optional(),
23345
+ plan_branch: string5().min(1).optional()
23346
+ }).strict()
23181
23347
  }).strict(),
23182
23348
  object4({
23183
23349
  operation: literal3("git.push"),
@@ -23412,7 +23578,7 @@ function resolveRequirements(input) {
23412
23578
  ].join(", ")}`,
23413
23579
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
23414
23580
  before: "dependent_action",
23415
- dependentAction: "dependent-action",
23581
+ dependentAction: null,
23416
23582
  acceptanceAllowed: false
23417
23583
  }));
23418
23584
  if (signals.productChoiceOpen.value === true)
@@ -23423,7 +23589,7 @@ function resolveRequirements(input) {
23423
23589
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
23424
23590
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
23425
23591
  before: "dependent_action",
23426
- dependentAction: "dependent-action",
23592
+ dependentAction: null,
23427
23593
  acceptanceAllowed: false
23428
23594
  }));
23429
23595
  if (signals.behaviorChange.value === true)
@@ -23581,59 +23747,6 @@ function diffPolicy(previous, next, reason, now) {
23581
23747
  return parsed.data;
23582
23748
  }
23583
23749
 
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
23750
  // packages/workit-core/src/core/task-engine.ts
23638
23751
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
23639
23752
  kind,
@@ -23764,15 +23877,20 @@ var applyWorkerLifecycle = (input) => {
23764
23877
  return failure("invalid_transition", "worker has not started");
23765
23878
  if (input.state === "running" && entry.data.state === "stopped")
23766
23879
  return failure("invalid_transition", "stopped worker cannot run again");
23880
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
23767
23881
  const nextEntry = {
23768
23882
  ...entry,
23769
23883
  recordedAt: input.now ?? entry.recordedAt,
23770
23884
  provenance: authority.provenance,
23771
- data: { ...entry.data, state: input.state, session: input.session }
23885
+ data: {
23886
+ ...entry.data,
23887
+ state: terminalCancel ? "cancelling" : input.state,
23888
+ session: input.session
23889
+ }
23772
23890
  };
23773
23891
  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
23892
  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)
23893
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
23776
23894
  return success(task.data.revision, workspace.data.revision, entry);
23777
23895
  const changed = input.store.mutateTaskAndWorkspace({
23778
23896
  taskId: task.data.id,
@@ -24142,7 +24260,7 @@ class WorkitCore {
24142
24260
  activeWorkerBlocker(task, workspace) {
24143
24261
  if (workspace.writer)
24144
24262
  return failure("recovery_required", "writer ownership must be released first");
24145
- if (task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
24263
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
24146
24264
  return failure("recovery_required", "worker state requires reconciliation");
24147
24265
  return null;
24148
24266
  }
@@ -24379,11 +24497,6 @@ class WorkitCore {
24379
24497
  if (helper && !helper.ok)
24380
24498
  return helper;
24381
24499
  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
24500
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
24388
24501
  return failure("invalid_input", "missing or skipped evidence requires a reason");
24389
24502
  if (evidence.kind === "review") {
@@ -24400,6 +24513,11 @@ class WorkitCore {
24400
24513
  evidence.beforeCandidateId ??= null;
24401
24514
  evidence.candidateId ??= null;
24402
24515
  }
24516
+ if (helper?.ok) {
24517
+ const assignment = helper.data.data.assignment;
24518
+ 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))
24519
+ return failure("permission_denied", "evidence is outside the worker assignment");
24520
+ }
24403
24521
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
24404
24522
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
24405
24523
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -24453,7 +24571,12 @@ class WorkitCore {
24453
24571
  const input = parsed.data;
24454
24572
  if (nativeRequired && input.action !== "record")
24455
24573
  return failure("permission_denied", "native observation is only valid for recording decisions");
24456
- if (nativeRequired && nativeObservation === undefined)
24574
+ const stated = input.action === "record" && input.response === "stated";
24575
+ if (stated && input.purpose === "action")
24576
+ return failure("permission_denied", "stated choices cannot authorize mutating actions");
24577
+ if (stated && !input.binding?.statedChoice)
24578
+ return failure("invalid_input", "stated choices require binding.statedChoice");
24579
+ if (nativeRequired && nativeObservation === undefined && !stated)
24457
24580
  return failure("permission_denied", "native decision observation is required");
24458
24581
  const task = this.store.readTask(input.taskId);
24459
24582
  if (!task.ok)
@@ -24486,7 +24609,7 @@ class WorkitCore {
24486
24609
  consumption: null
24487
24610
  };
24488
24611
  const data = { ...base, digest: decisionDigest(base) };
24489
- const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
24612
+ const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
24490
24613
  observation: nativeObservation,
24491
24614
  expected: {
24492
24615
  taskId: task.data.id,
@@ -25295,6 +25418,9 @@ class WorkitCore {
25295
25418
  id: task.id,
25296
25419
  revision: task.revision,
25297
25420
  workspaceRevision: workspace.data.revision,
25421
+ createdAt: task.createdAt,
25422
+ updatedAt: task.updatedAt,
25423
+ runtime: task.runtime ?? null,
25298
25424
  objective: task.intent.data.objective,
25299
25425
  status: task.status,
25300
25426
  closure: task.closure,
@@ -25380,7 +25506,7 @@ class WorkitCore {
25380
25506
  const base = reconcileResume(effectiveView);
25381
25507
  if (!base.ok)
25382
25508
  return base;
25383
- const needsReconciliation = view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
25509
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
25384
25510
  const blockers = [...base.data.blockers];
25385
25511
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
25386
25512
  blockers.push({
@@ -25447,7 +25573,8 @@ class WorkitCore {
25447
25573
  ...current,
25448
25574
  status,
25449
25575
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
25450
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
25576
+ progress: current.progress,
25577
+ pauseReason: status === "paused" ? input.reason ?? null : null
25451
25578
  })
25452
25579
  });
25453
25580
  if (!changed.ok)
@@ -25483,7 +25610,7 @@ class WorkitCore {
25483
25610
  return workspace;
25484
25611
  if (!workspace.data)
25485
25612
  return failure("not_found", "workspace not found");
25486
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
25613
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
25487
25614
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
25488
25615
  this.fillRevisions(input, task.data, workspace.data);
25489
25616
  const changed = this.store.mutateTaskAndWorkspace({
@@ -25594,15 +25721,15 @@ function resolveGitRevision(root, value) {
25594
25721
  }
25595
25722
  function resolveInside(root, candidate) {
25596
25723
  const base = realpathSync5(root);
25597
- const target = path6.resolve(base, candidate);
25598
- if (target !== base && !target.startsWith(base + path6.sep)) {
25724
+ const target = path7.resolve(base, candidate);
25725
+ if (target !== base && !target.startsWith(base + path7.sep)) {
25599
25726
  throw new Error("path must stay inside repository root");
25600
25727
  }
25601
25728
  let ancestor = target;
25602
- while (!existsSync3(ancestor))
25603
- ancestor = path6.dirname(ancestor);
25729
+ while (!existsSync4(ancestor))
25730
+ ancestor = path7.dirname(ancestor);
25604
25731
  const canonicalAncestor = realpathSync5(ancestor);
25605
- if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path6.sep)) {
25732
+ if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
25606
25733
  throw new Error("path must stay inside repository root");
25607
25734
  }
25608
25735
  return target;
@@ -25787,14 +25914,14 @@ var gitContext = (workspaceRoot, paths = []) => {
25787
25914
  import {
25788
25915
  copyFileSync,
25789
25916
  cpSync,
25790
- existsSync as existsSync4,
25917
+ existsSync as existsSync5,
25791
25918
  mkdirSync as mkdirSync2,
25792
25919
  readFileSync as readFileSync4,
25793
25920
  readdirSync as readdirSync3,
25794
25921
  writeFileSync as writeFileSync2
25795
25922
  } from "node:fs";
25796
25923
  import os2 from "node:os";
25797
- import path7 from "node:path";
25924
+ import path8 from "node:path";
25798
25925
  var diagnosticLogger;
25799
25926
  var PRESETS = {
25800
25927
  gitflow: {
@@ -25815,7 +25942,7 @@ var mergePreset = (preset, input = {}, current = {
25815
25942
  protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
25816
25943
  };
25817
25944
  };
25818
- 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");
25945
+ 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");
25819
25946
  var migratedDir = null;
25820
25947
  var migrationFailed = false;
25821
25948
  var ensureConfigDir = (dir = resolveConfigDir()) => {
@@ -25825,12 +25952,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25825
25952
  migratedDir = dir;
25826
25953
  return dir;
25827
25954
  }
25828
- const legacy = path7.join(process.env.XDG_CONFIG_HOME || path7.join(os2.homedir(), ".config"), "workflow-toolkit");
25829
- if (!existsSync4(legacy)) {
25955
+ const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
25956
+ if (!existsSync5(legacy)) {
25830
25957
  migratedDir = dir;
25831
25958
  return dir;
25832
25959
  }
25833
- if (!migrationFailed && existsSync4(dir)) {
25960
+ if (!migrationFailed && existsSync5(dir)) {
25834
25961
  migratedDir = dir;
25835
25962
  return dir;
25836
25963
  }
@@ -25838,9 +25965,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
25838
25965
  mkdirSync2(dir, { recursive: true });
25839
25966
  diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
25840
25967
  for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
25841
- const src = path7.join(legacy, entry.name);
25842
- const dest = path7.join(dir, entry.name);
25843
- if (existsSync4(dest))
25968
+ const src = path8.join(legacy, entry.name);
25969
+ const dest = path8.join(dir, entry.name);
25970
+ if (existsSync5(dest))
25844
25971
  continue;
25845
25972
  try {
25846
25973
  if (entry.isDirectory())
@@ -25913,7 +26040,7 @@ var parseConfigResult = (raw, file) => {
25913
26040
  };
25914
26041
  };
25915
26042
  var readConfigTyped = (dir) => {
25916
- const file = path7.join(dir ?? configDir(), "config.json");
26043
+ const file = path8.join(dir ?? configDir(), "config.json");
25917
26044
  return parseConfigResult(readSafe(file), file);
25918
26045
  };
25919
26046
  var readConfig = () => {
@@ -25949,9 +26076,9 @@ var COMMIT_PRESETS = [
25949
26076
 
25950
26077
  // packages/workit-core/src/core/workspaces.ts
25951
26078
  import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
25952
- import path8 from "node:path";
26079
+ import path9 from "node:path";
25953
26080
  var readWorkspacesResult = (dir = configDir()) => {
25954
- const file = path8.join(dir, "workspaces.json");
26081
+ const file = path9.join(dir, "workspaces.json");
25955
26082
  let raw;
25956
26083
  try {
25957
26084
  raw = readFileSync5(file, "utf8");
@@ -26049,10 +26176,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
26049
26176
 
26050
26177
  // packages/workit-core/src/core/vcs-config.ts
26051
26178
  import fs7 from "node:fs";
26052
- import path9 from "node:path";
26179
+ import path10 from "node:path";
26053
26180
  import { spawnSync as spawnSync3 } from "node:child_process";
26054
26181
  var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
26055
- var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path9.join(configDir(), "vcs.json");
26182
+ var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
26056
26183
  var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
26057
26184
  var remoteProvider = (cwd) => {
26058
26185
  const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
@@ -26135,8 +26262,8 @@ function vcsConfig(mode, cwd) {
26135
26262
  }
26136
26263
  const prov = cfg[provider] ?? {};
26137
26264
  const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
26138
- const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path9.join(configDir(), `${provider}.token`));
26139
- const tokenPath = path9.resolve(tokenFile);
26265
+ const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
26266
+ const tokenPath = path10.resolve(tokenFile);
26140
26267
  let tokenOk = false;
26141
26268
  if (fs7.existsSync(tokenPath)) {
26142
26269
  const token = fs7.readFileSync(tokenPath, "utf8").trim();
@@ -26145,7 +26272,7 @@ function vcsConfig(mode, cwd) {
26145
26272
  }
26146
26273
  const out = {
26147
26274
  ok: true,
26148
- configPath: path9.resolve(cfgPath),
26275
+ configPath: path10.resolve(cfgPath),
26149
26276
  provider,
26150
26277
  defaultTargetBranch: defaultTarget,
26151
26278
  pr: cfg.pr ?? {},
@@ -26238,18 +26365,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
26238
26365
 
26239
26366
  // packages/workit-core/src/core/pr-create.ts
26240
26367
  import fs8 from "node:fs";
26241
- import path10 from "node:path";
26368
+ import path11 from "node:path";
26242
26369
  function parseGhIssue(value) {
26243
26370
  const m = /issues\/(\d+)/.exec(value);
26244
26371
  return m ? m[1] : String(value).trim().replace(/^#/, "");
26245
26372
  }
26246
26373
  function whichOnPath(tool) {
26247
26374
  const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
26248
- for (const dir of (process.env.PATH ?? "").split(path10.delimiter)) {
26375
+ for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
26249
26376
  if (!dir)
26250
26377
  continue;
26251
26378
  for (const name of names) {
26252
- const candidate = path10.join(dir, name);
26379
+ const candidate = path11.join(dir, name);
26253
26380
  try {
26254
26381
  fs8.accessSync(candidate, fs8.constants.X_OK);
26255
26382
  return candidate;
@@ -26262,8 +26389,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
26262
26389
 
26263
26390
  // packages/workit-core/src/core/repo-context.ts
26264
26391
  import { spawnSync as spawnSync4 } from "node:child_process";
26265
- import { existsSync as existsSync5, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26266
- import path11 from "node:path";
26392
+ import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
26393
+ import path12 from "node:path";
26267
26394
  var isSafeContextRange = (value) => value.length <= 4096 && (() => {
26268
26395
  try {
26269
26396
  gitRevisionParts(value);
@@ -26397,7 +26524,7 @@ var findPrTemplate = (cwd) => {
26397
26524
  ]) {
26398
26525
  const parts = rel.split("/");
26399
26526
  const base = parts[parts.length - 1];
26400
- const dir = parts.length > 1 ? path11.join(cwd, ...parts.slice(0, -1)) : cwd;
26527
+ const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
26401
26528
  const actual = probeCaseInsensitive(dir, base);
26402
26529
  if (actual)
26403
26530
  return [...parts.slice(0, -1), actual].join("/");
@@ -26428,8 +26555,8 @@ var diffStatForRange = (cwd, range) => {
26428
26555
  return runGit(cwd, ["diff", "--stat", "--"]).stdout;
26429
26556
  };
26430
26557
  var packageScripts = (cwd, keys) => {
26431
- const pkgPath = path11.join(cwd, "package.json");
26432
- if (!existsSync5(pkgPath))
26558
+ const pkgPath = path12.join(cwd, "package.json");
26559
+ if (!existsSync6(pkgPath))
26433
26560
  return [];
26434
26561
  try {
26435
26562
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
@@ -26454,9 +26581,9 @@ var documentationFiles = (cwd) => {
26454
26581
  if (entry.isDirectory()) {
26455
26582
  if (excluded.has(entry.name))
26456
26583
  continue;
26457
- walk(path11.join(dir, entry.name), depth + 1);
26584
+ walk(path12.join(dir, entry.name), depth + 1);
26458
26585
  } else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
26459
- matches.push("./" + path11.relative(cwd, path11.join(dir, entry.name)).replaceAll("\\", "/"));
26586
+ matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
26460
26587
  }
26461
26588
  }
26462
26589
  };
@@ -26464,7 +26591,7 @@ var documentationFiles = (cwd) => {
26464
26591
  return matches.sort().slice(0, 200);
26465
26592
  };
26466
26593
  var readTrimmed = (file, maxLines) => {
26467
- if (!existsSync5(file))
26594
+ if (!existsSync6(file))
26468
26595
  return "";
26469
26596
  const lines = readFileSync6(file, "utf8").split(`
26470
26597
  `);
@@ -26472,7 +26599,7 @@ var readTrimmed = (file, maxLines) => {
26472
26599
  `);
26473
26600
  };
26474
26601
  function prReadyContext(root, range) {
26475
- const cwd = path11.resolve(repoRoot(root));
26602
+ const cwd = path12.resolve(repoRoot(root));
26476
26603
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26477
26604
  return invalidRange(cwd);
26478
26605
  let stdout = "";
@@ -26530,7 +26657,7 @@ ${status.stderr}` : "");
26530
26657
  stdout += `template_path: ${template}
26531
26658
 
26532
26659
  `;
26533
- stdout += readTrimmed(path11.join(cwd, template), 220);
26660
+ stdout += readTrimmed(path12.join(cwd, template), 220);
26534
26661
  } else {
26535
26662
  stdout += `template_path: none
26536
26663
 
@@ -26540,7 +26667,7 @@ ${status.stderr}` : "");
26540
26667
  stdout += `
26541
26668
  `;
26542
26669
  stdout += printSection("Recent Validation Signals");
26543
- if (existsSync5(path11.join(cwd, "package.json"))) {
26670
+ if (existsSync6(path12.join(cwd, "package.json"))) {
26544
26671
  stdout += `package.json detected. Common scripts:
26545
26672
  `;
26546
26673
  const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
@@ -26549,7 +26676,7 @@ ${status.stderr}` : "");
26549
26676
  `) + `
26550
26677
  `;
26551
26678
  }
26552
- if (existsSync5(path11.join(cwd, "Cargo.toml")) || existsSync5(path11.join(cwd, "src-tauri/Cargo.toml"))) {
26679
+ if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
26553
26680
  stdout += `Rust project detected.
26554
26681
  `;
26555
26682
  }
@@ -26582,7 +26709,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
26582
26709
  - Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
26583
26710
  - If Unreleased already has duplicate category headings, normalize_only first.`;
26584
26711
  function changelogContext(root, range) {
26585
- const cwd = path11.resolve(repoRoot(root));
26712
+ const cwd = path12.resolve(repoRoot(root));
26586
26713
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26587
26714
  return invalidRange(cwd);
26588
26715
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26600,8 +26727,8 @@ function changelogContext(root, range) {
26600
26727
  stdout += CHANGELOG_RULES + `
26601
26728
  `;
26602
26729
  stdout += printSection("Existing CHANGELOG.md");
26603
- const changelogPath = path11.join(cwd, "CHANGELOG.md");
26604
- if (existsSync5(changelogPath)) {
26730
+ const changelogPath = path12.join(cwd, "CHANGELOG.md");
26731
+ if (existsSync6(changelogPath)) {
26605
26732
  stdout += readTrimmed(changelogPath, 260) + `
26606
26733
  `;
26607
26734
  } else {
@@ -26620,7 +26747,7 @@ function changelogContext(root, range) {
26620
26747
  return { stdout, stderr: "", exitCode: 0, cwd };
26621
26748
  }
26622
26749
  function docsRefreshContext(root, range) {
26623
- const cwd = path11.resolve(repoRoot(root));
26750
+ const cwd = path12.resolve(repoRoot(root));
26624
26751
  if (range !== undefined && !isResolvableContextRange(cwd, range))
26625
26752
  return invalidRange(cwd);
26626
26753
  const resolvedRange = rangeArgOrDefault(range, cwd);
@@ -26642,8 +26769,8 @@ function docsRefreshContext(root, range) {
26642
26769
  `) + `
26643
26770
  `;
26644
26771
  stdout += printSection("README Preview");
26645
- const readme = path11.join(cwd, "README.md");
26646
- if (existsSync5(readme)) {
26772
+ const readme = path12.join(cwd, "README.md");
26773
+ if (existsSync6(readme)) {
26647
26774
  stdout += readTrimmed(readme, 220) + `
26648
26775
  `;
26649
26776
  } else {
@@ -26651,8 +26778,8 @@ function docsRefreshContext(root, range) {
26651
26778
  `;
26652
26779
  }
26653
26780
  stdout += printSection("Package Scripts");
26654
- const pkgPath = path11.join(cwd, "package.json");
26655
- if (existsSync5(pkgPath)) {
26781
+ const pkgPath = path12.join(cwd, "package.json");
26782
+ if (existsSync6(pkgPath)) {
26656
26783
  try {
26657
26784
  const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
26658
26785
  stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
@@ -26665,7 +26792,7 @@ function docsRefreshContext(root, range) {
26665
26792
  return { stdout, stderr: "", exitCode: 0, cwd };
26666
26793
  }
26667
26794
  function releaseNotesContext(root, rangeOrTag) {
26668
- const cwd = path11.resolve(repoRoot(root));
26795
+ const cwd = path12.resolve(repoRoot(root));
26669
26796
  if (!rangeOrTag) {
26670
26797
  return { stdout: "", stderr: `ERROR: release tag or range required
26671
26798
  `, exitCode: 1, cwd };
@@ -26703,7 +26830,7 @@ function releaseNotesContext(root, rangeOrTag) {
26703
26830
  `;
26704
26831
  stdout += printSection("Existing Release Files");
26705
26832
  for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
26706
- if (existsSync5(path11.join(cwd, rel)))
26833
+ if (existsSync6(path12.join(cwd, rel)))
26707
26834
  stdout += rel + `
26708
26835
  `;
26709
26836
  }
@@ -26894,25 +27021,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
26894
27021
  import fs10 from "node:fs";
26895
27022
  import path13 from "node:path";
26896
27023
 
26897
- // packages/workit-core/src/core/package-root.ts
26898
- import { existsSync as existsSync6 } from "node:fs";
26899
- import path12 from "node:path";
26900
- import { fileURLToPath } from "node:url";
26901
- var packageRoot = () => {
26902
- let dir = path12.dirname(fileURLToPath(import.meta.url));
26903
- while (true) {
26904
- const parent = path12.dirname(dir);
26905
- if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
26906
- return dir;
26907
- dir = parent;
26908
- }
26909
- };
26910
- var assetRoot = () => {
26911
- const root = packageRoot();
26912
- const assets = path12.join(root, "assets");
26913
- return existsSync6(assets) ? assets : root;
26914
- };
26915
-
26916
27024
  // packages/workit-core/src/core/templates.ts
26917
27025
  var repoRoot2 = assetRoot();
26918
27026
 
@@ -27416,6 +27524,32 @@ var toolInputSchema = (family) => {
27416
27524
  const schema = boundedOperationJsonSchema(family);
27417
27525
  return { type: "object", ...schema };
27418
27526
  };
27527
+ var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
27528
+ var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
27529
+ const action = branch.properties?.action?.const;
27530
+ return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
27531
+ });
27532
+ var advertisedToolSchemas = (attested) => {
27533
+ if (attested)
27534
+ return OPERATION_FAMILIES.map((family) => ({
27535
+ name: `workit_${family}`,
27536
+ description: operationDescription(family),
27537
+ inputSchema: toolInputSchema(family)
27538
+ }));
27539
+ return OPERATION_FAMILIES.flatMap((family) => {
27540
+ const branches = readOnlyBranches(family);
27541
+ if (!branches.length)
27542
+ return [];
27543
+ const schema = boundedOperationJsonSchema(family);
27544
+ return [
27545
+ {
27546
+ name: `workit_${family}`,
27547
+ description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
27548
+ inputSchema: { type: "object", ...schema, oneOf: branches }
27549
+ }
27550
+ ];
27551
+ });
27552
+ };
27419
27553
  var sanitizeFailure = (result, workspaceRoot) => {
27420
27554
  if (result.ok)
27421
27555
  return result;
@@ -27470,13 +27604,16 @@ function createMcpServer(host, contextProvider) {
27470
27604
  ].filter((file) => existsSync7(file)));
27471
27605
  let staleWarned = false;
27472
27606
  const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
27473
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
27474
- tools: OPERATION_FAMILIES.map((family) => ({
27475
- name: `workit_${family}`,
27476
- description: operationDescription(family),
27477
- inputSchema: toolInputSchema(family)
27478
- }))
27479
- }));
27607
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
27608
+ let attested = false;
27609
+ try {
27610
+ const context = await contextProvider.current();
27611
+ attested = context.callerAttested === true;
27612
+ } catch {
27613
+ attested = false;
27614
+ }
27615
+ return { tools: advertisedToolSchemas(attested) };
27616
+ });
27480
27617
  server.setRequestHandler(ListResourcesRequestSchema, async () => ({
27481
27618
  resources: CONTEXT_KINDS.map((kind) => ({
27482
27619
  uri: `workit://context/${kind}`,
@@ -27591,7 +27728,7 @@ function createMcpServer(host, contextProvider) {
27591
27728
  const parsed = parseOperation(family, request.params.arguments);
27592
27729
  if (!parsed.ok)
27593
27730
  return resultForClient(parsed, workspaceRoot);
27594
- if (context.callerAttested === false && requiresCallerIdentity(parsed.data))
27731
+ if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
27595
27732
  return resultForClient({
27596
27733
  ok: false,
27597
27734
  schemaVersion: 1,
@@ -27680,6 +27817,13 @@ var cursorCapabilities = (availability = {}) => {
27680
27817
  reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
27681
27818
  refs: [hostRef("subagentStart")]
27682
27819
  },
27820
+ {
27821
+ name: "fresh-context-review",
27822
+ surface: "subagentStart",
27823
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
27824
+ 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",
27825
+ refs: [hostRef("subagentStart")]
27826
+ },
27683
27827
  {
27684
27828
  name: "arbitrary_shell_write",
27685
27829
  surface: "unobservable_shell",