@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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // packages/workit-cursor/hooks/workit-hook.ts
6
- import { existsSync as existsSync3, realpathSync as realpathSync5 } from "node:fs";
7
- import path6 from "node:path";
6
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
7
+ import path7 from "node:path";
8
8
 
9
9
  // packages/workit-core/src/core/task-contract.ts
10
10
  import { createHash, randomUUID } from "node:crypto";
@@ -7573,10 +7573,12 @@ var decisionSchema = object({
7573
7573
  scope: scopeSchema,
7574
7574
  presented: text,
7575
7575
  approvedContent: text,
7576
- contentRefs: array(refSchema)
7576
+ displayed: text.optional(),
7577
+ contentRefs: array(refSchema),
7578
+ statedChoice: object({ ref: text, text }).strict().optional()
7577
7579
  }).strict(),
7578
7580
  digest,
7579
- response: _enum(["approved", "rejected"]),
7581
+ response: _enum(["approved", "rejected", "stated"]),
7580
7582
  requirementIds: array(digest),
7581
7583
  revoked: object({ at: utc, reason: text }).strict().nullable(),
7582
7584
  consumption: object({
@@ -7651,6 +7653,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
7651
7653
  path: ["decisionId"]
7652
7654
  });
7653
7655
  });
7656
+ var runtimeSchema = object({
7657
+ createdWith: text.nullable(),
7658
+ updatedWith: text
7659
+ }).strict();
7654
7660
  var taskRecordSchema = object({
7655
7661
  schemaVersion: literal(1),
7656
7662
  id,
@@ -7664,6 +7670,8 @@ var taskRecordSchema = object({
7664
7670
  status: _enum(["active", "paused", "closed"]),
7665
7671
  closure: closureSchema.nullable(),
7666
7672
  progress: progressSchema,
7673
+ pauseReason: text.nullable().optional(),
7674
+ runtime: runtimeSchema.optional(),
7667
7675
  assessments: array(entrySchema(assessmentSchema)),
7668
7676
  policy: policySchema.nullable(),
7669
7677
  policyChanges: array(policyChangeSchema),
@@ -7679,6 +7687,7 @@ var workspaceRecordSchema = object({
7679
7687
  id,
7680
7688
  revision,
7681
7689
  root: nonEmpty,
7690
+ runtime: runtimeSchema.optional(),
7682
7691
  writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
7683
7692
  }).strict();
7684
7693
  var capabilitySchema = object({
@@ -7706,6 +7715,9 @@ var taskSummarySchema = object({
7706
7715
  id,
7707
7716
  revision,
7708
7717
  workspaceRevision: revision,
7718
+ createdAt: utc,
7719
+ updatedAt: utc,
7720
+ runtime: runtimeSchema.nullable(),
7709
7721
  objective: text,
7710
7722
  status: _enum(["active", "paused", "closed"]),
7711
7723
  closure: closureSchema.nullable(),
@@ -7776,7 +7788,7 @@ var taskOperations = {
7776
7788
  expectedWorkspaceRevision: revision.optional(),
7777
7789
  outcome: outcomeSchema,
7778
7790
  summary: text,
7779
- decisionIds: array(id)
7791
+ decisionIds: array(id).optional()
7780
7792
  })
7781
7793
  };
7782
7794
  var policyOperations = {
@@ -7868,14 +7880,15 @@ var writerOperations = {
7868
7880
  ...taskId,
7869
7881
  expectedRevision: revision.optional(),
7870
7882
  expectedWorkspaceRevision: revision.optional(),
7871
- workerId: nullableId.optional()
7883
+ workerId: nullableId.optional(),
7884
+ reason: text.optional()
7872
7885
  }),
7873
7886
  release: operation({
7874
7887
  action: literal("release"),
7875
7888
  ...taskId,
7876
7889
  expectedRevision: revision.optional(),
7877
7890
  expectedWorkspaceRevision: revision.optional(),
7878
- reason: text
7891
+ reason: text.optional()
7879
7892
  })
7880
7893
  };
7881
7894
  var stateOperations = {
@@ -8026,7 +8039,21 @@ function requirementId(input) {
8026
8039
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8027
8040
  import * as fs4 from "node:fs";
8028
8041
  import { hostname } from "node:os";
8029
- import path3 from "node:path";
8042
+ import path4 from "node:path";
8043
+
8044
+ // packages/workit-core/src/core/package-root.ts
8045
+ import { existsSync } from "node:fs";
8046
+ import path from "node:path";
8047
+ import { fileURLToPath } from "node:url";
8048
+ var packageRoot = () => {
8049
+ let dir = path.dirname(fileURLToPath(import.meta.url));
8050
+ while (true) {
8051
+ const parent = path.dirname(dir);
8052
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
8053
+ return dir;
8054
+ dir = parent;
8055
+ }
8056
+ };
8030
8057
 
8031
8058
  // node_modules/@openclaw/fs-safe/dist/errors.js
8032
8059
  var OPERATIONAL_CODES = new Set([
@@ -8077,7 +8104,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
8077
8104
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
8078
8105
  import { randomBytes } from "node:crypto";
8079
8106
  import fsSync from "node:fs";
8080
- import path from "node:path";
8107
+ import path2 from "node:path";
8081
8108
 
8082
8109
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
8083
8110
  import fs from "node:fs";
@@ -8167,13 +8194,13 @@ ${ownershipToken}
8167
8194
  return { raw, ownershipToken };
8168
8195
  }
8169
8196
  function relativeSidecarLockPath(lockRoot, lockPath) {
8170
- const resolved = path.resolve(lockPath);
8171
- const lexicalRelative = path.relative(lockRoot.rootDir, resolved);
8172
- const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path.sep}`) && !path.isAbsolute(lexicalRelative) ? lexicalRelative : path.relative(lockRoot.rootReal, resolved);
8173
- if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
8197
+ const resolved = path2.resolve(lockPath);
8198
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
8199
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
8200
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
8174
8201
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
8175
8202
  }
8176
- return relative.split(path.sep).join(path.posix.sep);
8203
+ return relative.split(path2.sep).join(path2.posix.sep);
8177
8204
  }
8178
8205
  function parseSidecarLockPayload(raw, parser) {
8179
8206
  if (parser) {
@@ -8320,7 +8347,7 @@ function getFsSafeLockConfig() {
8320
8347
 
8321
8348
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
8322
8349
  import fs3 from "node:fs";
8323
- import path2 from "node:path";
8350
+ import path3 from "node:path";
8324
8351
 
8325
8352
  // node_modules/@openclaw/fs-safe/dist/timing.js
8326
8353
  function sleepSync(ms) {
@@ -8411,26 +8438,26 @@ function canonicalLockParentSync(parent) {
8411
8438
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
8412
8439
  }
8413
8440
  function normalizeTargetPath(targetPath) {
8414
- const resolved = path2.resolve(targetPath);
8415
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
8441
+ const resolved = path3.resolve(targetPath);
8442
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
8416
8443
  try {
8417
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
8444
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
8418
8445
  } catch {
8419
8446
  return resolved;
8420
8447
  }
8421
8448
  }
8422
8449
  function boundedLockPath(lockPath, lockRoot) {
8423
- const resolved = path2.resolve(lockPath);
8450
+ const resolved = path3.resolve(lockPath);
8424
8451
  if (!lockRoot)
8425
8452
  return resolved;
8426
8453
  relativeSidecarLockPath(lockRoot, resolved);
8427
- const parent = path2.dirname(resolved);
8454
+ const parent = path3.dirname(resolved);
8428
8455
  const parentReal = canonicalLockParentSync(parent);
8429
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
8430
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
8456
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
8457
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
8431
8458
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
8432
8459
  }
8433
- return path2.join(parentReal, path2.basename(resolved));
8460
+ return path3.join(parentReal, path3.basename(resolved));
8434
8461
  }
8435
8462
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
8436
8463
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -8643,6 +8670,33 @@ var metadataLockSchema = object({
8643
8670
  nonce: string2().min(1)
8644
8671
  }).strict();
8645
8672
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
8673
+ var cachedRuntimeVersion = null;
8674
+ var runtimeVersion = () => {
8675
+ if (cachedRuntimeVersion === null) {
8676
+ try {
8677
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
8678
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
8679
+ } catch {
8680
+ cachedRuntimeVersion = "0.0.0";
8681
+ }
8682
+ }
8683
+ return cachedRuntimeVersion;
8684
+ };
8685
+ var stampNew = (record) => ({
8686
+ createdWith: record.runtime?.createdWith ?? null,
8687
+ updatedWith: runtimeVersion()
8688
+ });
8689
+ var isNewerVersion = (candidate, current) => {
8690
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
8691
+ const [next, now] = [parts(candidate), parts(current)];
8692
+ for (let index = 0;index < 3; index += 1) {
8693
+ const left = next[index] ?? 0;
8694
+ const right = now[index] ?? 0;
8695
+ if (left !== right)
8696
+ return left > right;
8697
+ }
8698
+ return false;
8699
+ };
8646
8700
  var jsonBytes = (value) => `${canonicalJson(value)}
8647
8701
  `;
8648
8702
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -8669,12 +8723,12 @@ var sameMetadataLock = (left, right) => {
8669
8723
  class TaskStore {
8670
8724
  root;
8671
8725
  constructor(root) {
8672
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
8726
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
8673
8727
  }
8674
8728
  readTask(taskId) {
8675
8729
  if (!validId(taskId))
8676
8730
  return failure("invalid_input", "task ID is invalid", { taskId });
8677
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8731
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8678
8732
  if (!result.exists)
8679
8733
  return failure("not_found", "task not found", { taskId });
8680
8734
  if (result.result.ok && result.result.data.id !== taskId)
@@ -8708,7 +8762,7 @@ class TaskStore {
8708
8762
  });
8709
8763
  const tasks = [];
8710
8764
  for (const name of names.sort()) {
8711
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
8765
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
8712
8766
  if (!item.exists)
8713
8767
  continue;
8714
8768
  if (!item.result.ok)
@@ -8742,7 +8796,7 @@ class TaskStore {
8742
8796
  if (!current.ok)
8743
8797
  return current;
8744
8798
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
8745
- return failure("revision_conflict", "workspace revision does not match", {
8799
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8746
8800
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
8747
8801
  actualWorkspaceRevision: current.data?.revision ?? null
8748
8802
  });
@@ -8754,11 +8808,17 @@ class TaskStore {
8754
8808
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
8755
8809
  if (current.data && !previousWorkspaceBytes)
8756
8810
  return failure("storage_error", "workspace snapshot disappeared during creation");
8757
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8811
+ const workspace = current.data ? {
8812
+ ...current.data,
8813
+ revision: newRevision(),
8814
+ root: this.root,
8815
+ runtime: stampNew(current.data)
8816
+ } : {
8758
8817
  schemaVersion: SCHEMA_VERSION,
8759
8818
  id: newId(),
8760
8819
  revision: newRevision(),
8761
8820
  root: this.root,
8821
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8762
8822
  writer: null
8763
8823
  };
8764
8824
  const timestamp = value.now ?? now();
@@ -8780,6 +8840,7 @@ class TaskStore {
8780
8840
  status: "active",
8781
8841
  closure: null,
8782
8842
  progress: { summary: "", nextAction: null, blockers: [] },
8843
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8783
8844
  assessments: [],
8784
8845
  policy: null,
8785
8846
  policyChanges: [],
@@ -8810,7 +8871,7 @@ class TaskStore {
8810
8871
  if (!current.ok)
8811
8872
  return current;
8812
8873
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
8813
- return failure("revision_conflict", "workspace revision does not match", {
8874
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8814
8875
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
8815
8876
  actualWorkspaceRevision: current.data?.revision ?? null
8816
8877
  });
@@ -8818,11 +8879,17 @@ class TaskStore {
8818
8879
  if (current.data && !previousWorkspaceBytes)
8819
8880
  return failure("storage_error", "workspace snapshot disappeared during import");
8820
8881
  const timestamp = input.now ?? now();
8821
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8882
+ const workspace = current.data ? {
8883
+ ...current.data,
8884
+ revision: newRevision(),
8885
+ root: this.root,
8886
+ runtime: stampNew(current.data)
8887
+ } : {
8822
8888
  schemaVersion: SCHEMA_VERSION,
8823
8889
  id: input.workspaceId ?? newId(),
8824
8890
  revision: newRevision(),
8825
8891
  root: this.root,
8892
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8826
8893
  writer: null
8827
8894
  };
8828
8895
  const task = {
@@ -8830,7 +8897,11 @@ class TaskStore {
8830
8897
  workspaceId: workspace.id,
8831
8898
  revision: newRevision(),
8832
8899
  createdAt: timestamp,
8833
- updatedAt: timestamp
8900
+ updatedAt: timestamp,
8901
+ runtime: {
8902
+ createdWith: input.task.runtime?.createdWith ?? null,
8903
+ updatedWith: runtimeVersion()
8904
+ }
8834
8905
  };
8835
8906
  const validTask = taskRecordSchema.safeParse(task);
8836
8907
  if (!validTask.success)
@@ -8879,7 +8950,8 @@ class TaskStore {
8879
8950
  workspaceId: current.data.workspaceId,
8880
8951
  createdAt: current.data.createdAt,
8881
8952
  revision: context.revision,
8882
- updatedAt: context.now
8953
+ updatedAt: context.now,
8954
+ runtime: stampNew(current.data)
8883
8955
  };
8884
8956
  const valid = taskRecordSchema.safeParse(record);
8885
8957
  if (!valid.success)
@@ -8913,7 +8985,8 @@ class TaskStore {
8913
8985
  ...changed.data,
8914
8986
  id: current.data.id,
8915
8987
  root: this.root,
8916
- revision: context.revision
8988
+ revision: context.revision,
8989
+ runtime: stampNew(current.data)
8917
8990
  };
8918
8991
  const valid = workspaceRecordSchema.safeParse(record);
8919
8992
  if (!valid.success)
@@ -8956,7 +9029,8 @@ class TaskStore {
8956
9029
  ...changedWorkspace.data,
8957
9030
  id: workspace.data.id,
8958
9031
  root: this.root,
8959
- revision: workspaceContext.revision
9032
+ revision: workspaceContext.revision,
9033
+ runtime: stampNew(workspace.data)
8960
9034
  });
8961
9035
  if (!nextWorkspace.success)
8962
9036
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -8981,7 +9055,8 @@ class TaskStore {
8981
9055
  workspaceId: task.data.workspaceId,
8982
9056
  createdAt: task.data.createdAt,
8983
9057
  revision: taskContext.revision,
8984
- updatedAt: taskContext.now
9058
+ updatedAt: taskContext.now,
9059
+ runtime: stampNew(task.data)
8985
9060
  });
8986
9061
  if (!nextTask.success) {
8987
9062
  this.markUncertain(nextWorkspace.data);
@@ -9008,7 +9083,7 @@ class TaskStore {
9008
9083
  if (match)
9009
9084
  candidates.push({
9010
9085
  target: match[1],
9011
- path: path3.join(this.recoveryDir, name),
9086
+ path: path4.join(this.recoveryDir, name),
9012
9087
  digest: match[3]
9013
9088
  });
9014
9089
  }
@@ -9201,7 +9276,8 @@ class TaskStore {
9201
9276
  const value = {
9202
9277
  ...workspace,
9203
9278
  writer: { ...workspace.writer, state: "uncertain" },
9204
- revision: newRevision()
9279
+ revision: newRevision(),
9280
+ runtime: stampNew(workspace)
9205
9281
  };
9206
9282
  this.replaceSnapshot(this.workspacePath, value, workspace);
9207
9283
  }
@@ -9279,7 +9355,7 @@ class TaskStore {
9279
9355
  }
9280
9356
  fs4.renameSync(temporary, file);
9281
9357
  temporary = undefined;
9282
- this.fsyncDirectory(path3.dirname(file));
9358
+ this.fsyncDirectory(path4.dirname(file));
9283
9359
  return success(null, null, value);
9284
9360
  } catch (error) {
9285
9361
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -9293,9 +9369,9 @@ class TaskStore {
9293
9369
  }
9294
9370
  }
9295
9371
  saveRecovery(file, bytes) {
9296
- const target = path3.basename(file) === "workspace.json" ? "workspace" : "task";
9297
- const id = target === "task" ? path3.basename(file, ".json") : "workspace";
9298
- const destination = path3.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9372
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
9373
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
9374
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9299
9375
  if (fs4.existsSync(destination)) {
9300
9376
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
9301
9377
  return;
@@ -9373,7 +9449,7 @@ class TaskStore {
9373
9449
  for (const name of fs4.readdirSync(this.tasksDir)) {
9374
9450
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
9375
9451
  return null;
9376
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
9452
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
9377
9453
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
9378
9454
  return null;
9379
9455
  ids.add(parsed.data.workspaceId);
@@ -9415,7 +9491,12 @@ class TaskStore {
9415
9491
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
9416
9492
  return failure("unsupported_version", "unsupported snapshot schema version");
9417
9493
  const parsed = schema.safeParse(value);
9418
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
9494
+ if (parsed.success)
9495
+ return success(null, null, parsed.data);
9496
+ const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
9497
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
9498
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
9499
+ return failure("recovery_required", "snapshot does not satisfy its schema");
9419
9500
  }
9420
9501
  processStart(pid) {
9421
9502
  try {
@@ -9425,43 +9506,98 @@ class TaskStore {
9425
9506
  }
9426
9507
  }
9427
9508
  conflict(expected, actual) {
9428
- return failure("revision_conflict", "snapshot revision does not match", {
9509
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
9429
9510
  expectedRevision: expected,
9430
9511
  actualRevision: actual
9431
9512
  });
9432
9513
  }
9433
9514
  taskPath(taskId) {
9434
- return path3.join(this.tasksDir, `${taskId}.json`);
9515
+ return path4.join(this.tasksDir, `${taskId}.json`);
9435
9516
  }
9436
9517
  get workitDir() {
9437
- return path3.join(this.root, ".workit");
9518
+ return path4.join(this.root, ".workit");
9438
9519
  }
9439
9520
  get tasksDir() {
9440
- return path3.join(this.workitDir, "tasks");
9521
+ return path4.join(this.workitDir, "tasks");
9441
9522
  }
9442
9523
  get recoveryDir() {
9443
- return path3.join(this.workitDir, "recovery");
9524
+ return path4.join(this.workitDir, "recovery");
9444
9525
  }
9445
9526
  get workspacePath() {
9446
- return path3.join(this.workitDir, "workspace.json");
9527
+ return path4.join(this.workitDir, "workspace.json");
9447
9528
  }
9448
9529
  get lockPath() {
9449
- return path3.join(this.workitDir, "metadata.lock");
9530
+ return path4.join(this.workitDir, "metadata.lock");
9450
9531
  }
9451
9532
  get gitignorePath() {
9452
- return path3.join(this.workitDir, ".gitignore");
9533
+ return path4.join(this.workitDir, ".gitignore");
9534
+ }
9535
+ }
9536
+ // packages/workit-core/src/core/workers.ts
9537
+ var same = (left, right) => {
9538
+ try {
9539
+ return canonicalJson(left) === canonicalJson(right);
9540
+ } catch {
9541
+ return false;
9453
9542
  }
9543
+ };
9544
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9545
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
9546
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
9547
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9548
+ const code = char.charCodeAt(0);
9549
+ return code < 32 || code === 127;
9550
+ }) && !value.split(/[\\/]/).includes("..");
9551
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
9552
+ var workerIdOf = (value) => value.workerId ?? null;
9553
+ function assertProductWriteAllowed(input) {
9554
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9555
+ return failure("invalid_input", "invalid product write path");
9556
+ if (!input.store)
9557
+ return failure("permission_denied", "write authorization requires core state");
9558
+ const task = input.store.readTask(input.task.id);
9559
+ if (!task.ok)
9560
+ return task;
9561
+ const workspace = input.store.readWorkspace();
9562
+ if (!workspace.ok)
9563
+ return workspace;
9564
+ if (!workspace.data)
9565
+ return failure("not_found", "workspace not found");
9566
+ const workspaceRecord = workspace.data;
9567
+ if (task.data.status !== "active")
9568
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
9569
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
9570
+ return failure("recovery_required", "task and workspace bindings are invalid");
9571
+ const writer = workspaceRecord.writer;
9572
+ if (!writer)
9573
+ return failure("permission_denied", "checkout has no writer owner");
9574
+ if (writer.state === "uncertain")
9575
+ return failure("recovery_required", "checkout writer requires recovery", {
9576
+ owner: writer.owner
9577
+ });
9578
+ const caller = callerValue(input.caller);
9579
+ const workerId = workerIdOf(input.caller);
9580
+ const owner = writer.owner;
9581
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
9582
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
9583
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
9584
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
9585
+ return failure("permission_denied", "only implementers can own product writes");
9586
+ 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))))
9587
+ return failure("recovery_required", "worker session is not an active writer");
9588
+ return success(null, workspaceRecord.revision, owner);
9454
9589
  }
9590
+
9455
9591
  // packages/workit-core/src/core/task-evaluation.ts
9456
9592
  import { createHash as createHash4 } from "node:crypto";
9457
9593
  import * as fs6 from "node:fs";
9458
9594
  import { spawnSync } from "node:child_process";
9459
- import path5 from "node:path";
9595
+ import path6 from "node:path";
9460
9596
 
9461
9597
  // packages/workit-core/src/core/authority.ts
9462
9598
  import { createHash as createHash3 } from "node:crypto";
9463
9599
  import * as fs5 from "node:fs";
9464
- import path4 from "node:path";
9600
+ import path5 from "node:path";
9465
9601
  var verifiedAuthorities = new WeakMap;
9466
9602
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
9467
9603
  const token = {};
@@ -9588,20 +9724,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
9588
9724
  continue;
9589
9725
  if (!reference.digest)
9590
9726
  return failure("invalid_input", "document references require a byte digest");
9591
- const target = path4.resolve(root, reference.path);
9592
- if (target !== root && !target.startsWith(`${root}${path4.sep}`))
9593
- return failure("invalid_input", "document reference escapes checkout");
9727
+ const target = path5.resolve(root, reference.path);
9728
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
9729
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9730
+ fields: [
9731
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
9732
+ ]
9733
+ });
9594
9734
  try {
9595
- const relative = path4.relative(root, target);
9735
+ const relative = path5.relative(root, target);
9596
9736
  let current = root;
9597
- for (const segment of relative.split(path4.sep)) {
9737
+ for (const segment of relative.split(path5.sep)) {
9598
9738
  if (!segment)
9599
9739
  continue;
9600
- current = path4.join(current, segment);
9740
+ current = path5.join(current, segment);
9601
9741
  const stat = fs5.lstatSync(current);
9602
9742
  const real = fs5.realpathSync(current);
9603
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
9604
- return failure("invalid_input", "document reference escapes checkout");
9743
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
9744
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9745
+ fields: [
9746
+ {
9747
+ path: "contentRefs.path",
9748
+ reason: `${reference.path} resolves outside the checkout`
9749
+ }
9750
+ ]
9751
+ });
9605
9752
  if (stat.isSymbolicLink())
9606
9753
  return failure("invalid_input", "document reference uses a symlink");
9607
9754
  if (current === target && !stat.isFile())
@@ -9948,14 +10095,14 @@ var verifyDecisionContent = verifyContentRefs;
9948
10095
 
9949
10096
  // packages/workit-core/src/core/task-evaluation.ts
9950
10097
  var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
9951
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path5.sep}`);
9952
- var relative = (root, candidate) => path5.relative(root, candidate).split(path5.sep).join("/") || ".";
10098
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
10099
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
9953
10100
  var canonicalPath = (value) => {
9954
10101
  if (typeof value !== "string" || !value)
9955
10102
  return null;
9956
10103
  if (value.includes("\\") || value.split("/").includes(".."))
9957
10104
  return null;
9958
- const normalized = path5.posix.normalize(value);
10105
+ const normalized = path6.posix.normalize(value);
9959
10106
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
9960
10107
  return null;
9961
10108
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10003,14 +10150,14 @@ var gitPaths = (root) => {
10003
10150
  git: /not a git repository/i.test(stderr) ? false : true
10004
10151
  };
10005
10152
  }
10006
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
10153
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
10007
10154
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10008
10155
  if (stagedDeleted.status !== 0) {
10009
10156
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10010
10157
  if (!/not a git repository/i.test(stderr))
10011
10158
  return { paths, uncertain: true, git: true };
10012
10159
  } else if (stagedDeleted.stdout) {
10013
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
10160
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
10014
10161
  }
10015
10162
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10016
10163
  };
@@ -10025,7 +10172,7 @@ var walk = (root, directory, output) => {
10025
10172
  for (const name of names) {
10026
10173
  if (name === ".git" || name === ".workit")
10027
10174
  continue;
10028
- const target = path5.join(directory, name);
10175
+ const target = path6.join(directory, name);
10029
10176
  let stat;
10030
10177
  try {
10031
10178
  stat = fs6.lstatSync(target);
@@ -10043,17 +10190,17 @@ var walk = (root, directory, output) => {
10043
10190
  var scopeRoots = (root, scope) => {
10044
10191
  const roots = [];
10045
10192
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10046
- const target = path5.resolve(root, item);
10193
+ const target = path6.resolve(root, item);
10047
10194
  if (!inside(root, target))
10048
- return failure("invalid_input", "candidate scope escapes checkout");
10195
+ 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` }] });
10049
10196
  let ancestor = target;
10050
10197
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10051
- ancestor = path5.dirname(ancestor);
10198
+ ancestor = path6.dirname(ancestor);
10052
10199
  try {
10053
10200
  if (!inside(root, fs6.realpathSync(ancestor)))
10054
- return failure("invalid_input", "candidate scope escapes checkout");
10201
+ 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` }] });
10055
10202
  } catch {
10056
- return failure("invalid_input", "candidate scope cannot be inspected");
10203
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
10057
10204
  }
10058
10205
  roots.push(target);
10059
10206
  }
@@ -10068,7 +10215,7 @@ function captureCandidate(root, scope, environment = []) {
10068
10215
  }
10069
10216
  const normalizedScope = canonicalScope(scope);
10070
10217
  if (!normalizedScope)
10071
- return failure("invalid_input", "candidate scope is invalid");
10218
+ 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" }] });
10072
10219
  const roots = scopeRoots(checkout, normalizedScope);
10073
10220
  if (!roots.ok)
10074
10221
  return roots;
@@ -10100,7 +10247,7 @@ function captureCandidate(root, scope, environment = []) {
10100
10247
  for (const item of [...names].sort()) {
10101
10248
  if (!scopeMatches(item, normalizedScope))
10102
10249
  continue;
10103
- const target = path5.join(checkout, item);
10250
+ const target = path6.join(checkout, item);
10104
10251
  try {
10105
10252
  const stat = fs6.lstatSync(target);
10106
10253
  if (stat.isSymbolicLink()) {
@@ -10222,7 +10369,7 @@ function evaluateEvidence(task, candidate) {
10222
10369
  }
10223
10370
  var scopeCovers2 = scopeCovers;
10224
10371
  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")));
10225
- 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 }));
10372
+ 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 }));
10226
10373
  var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
10227
10374
  if (kind !== "passed")
10228
10375
  return false;
@@ -10257,7 +10404,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10257
10404
  return true;
10258
10405
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10259
10406
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10260
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
10407
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10261
10408
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10262
10409
  });
10263
10410
  if (passed.length) {
@@ -10298,7 +10445,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10298
10445
  status: "satisfied",
10299
10446
  evidenceIds: [],
10300
10447
  decisionIds: decisions.map((decision) => decision.id),
10301
- reason: "an applicable approved decision satisfies the requirement"
10448
+ reason: "an applicable approved or stated decision satisfies the requirement"
10302
10449
  };
10303
10450
  const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
10304
10451
  if (limitations.length)
@@ -10353,7 +10500,17 @@ function evaluateClosure(requestedOutcome, view) {
10353
10500
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10354
10501
  if (requestedOutcome !== "stopped" && blocking.length)
10355
10502
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10356
- requirementIds: blocking.map((item) => item.requirementId)
10503
+ requirementIds: blocking.map((item) => item.requirementId),
10504
+ requirements: blocking.map((item) => {
10505
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10506
+ return {
10507
+ requirementId: item.requirementId,
10508
+ ruleId: requirement?.ruleId ?? "unknown",
10509
+ reason: item.reason,
10510
+ satisfaction: requirement?.satisfaction ?? "",
10511
+ dependentAction: requirement?.dependentAction ?? null
10512
+ };
10513
+ })
10357
10514
  });
10358
10515
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
10359
10516
  if (requestedOutcome === "verified" && accepted.length)
@@ -10384,7 +10541,7 @@ function evaluateClosure(requestedOutcome, view) {
10384
10541
  var METHODS = {
10385
10542
  "workit-challenge": { dimensions: ["challenge", "decisions"] },
10386
10543
  "workit-behavioral-tdd": { dimensions: ["testing"] },
10387
- "workit-review": { dimensions: ["review"] },
10544
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
10388
10545
  "workit-plan": { dimensions: ["artifacts", "continuity"] },
10389
10546
  "workit-implement": { dimensions: ["delegation"] },
10390
10547
  "workit-debug": { ruleIds: ["root-cause-investigation"] },
@@ -10446,7 +10603,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
10446
10603
  approvedContent} only through a native approval question asked receipt-shaped:
10447
10604
  header Workit decision: <purpose>, the same label repeated in the question
10448
10605
  text (some host UIs do not render headers), exactly two options approved and
10449
- rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
10606
+ rejected; ask once, never re-ask to mint a receipt. Before asking, present the
10607
+ item in the conversation (durable artifacts: complete digest plus exact path;
10608
+ inline plans: the content) and keep the question to one short scoped sentence;
10609
+ never ask to approve something unseen. An answer with custom text is steering,
10610
+ not approval: adjust, re-present, then ask a new scoped question. After the user
10611
+ approves a spec or plan, execute the remaining tasks continuously with one
10612
+ atomic commit per task and do not ask whether to continue; stop only for a new
10613
+ product decision, a failed safety or verification gate, a conflicting edit, or
10614
+ missing authority. Record the plan's commit list once through the plan-scoped
10615
+ action approval (git.commit with plan_steps and plan_branch) so each listed
10616
+ commit executes without a new question; an unlisted message needs a fresh
10617
+ approval. task.close {outcome,
10450
10618
  summary, decisionIds}. A completed native subagent run stops its bound worker
10451
10619
  by itself; if a worker strands in cancelling with its run verifiably over,
10452
10620
  repeat worker.cancel {reason} to confirm the stop. Check and review evidence
@@ -10478,7 +10646,7 @@ function reconcileResume(view, observations = []) {
10478
10646
  return candidate;
10479
10647
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
10480
10648
  const blockers = [...view.task.progress.blockers];
10481
- if (view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
10649
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
10482
10650
  blockers.push({
10483
10651
  reason: "worker state requires reconciliation",
10484
10652
  dependentAction: "resume",
@@ -10582,7 +10750,11 @@ var externalActionSchema = discriminatedUnion("operation", [
10582
10750
  }).strict(),
10583
10751
  object({
10584
10752
  operation: literal("git.commit"),
10585
- payload: object({ message: string2().min(1) }).strict()
10753
+ payload: object({
10754
+ message: string2().min(1).optional(),
10755
+ plan_steps: array(string2().min(1)).optional(),
10756
+ plan_branch: string2().min(1).optional()
10757
+ }).strict()
10586
10758
  }).strict(),
10587
10759
  object({
10588
10760
  operation: literal("git.push"),
@@ -10817,7 +10989,7 @@ function resolveRequirements(input) {
10817
10989
  ].join(", ")}`,
10818
10990
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
10819
10991
  before: "dependent_action",
10820
- dependentAction: "dependent-action",
10992
+ dependentAction: null,
10821
10993
  acceptanceAllowed: false
10822
10994
  }));
10823
10995
  if (signals.productChoiceOpen.value === true)
@@ -10828,7 +11000,7 @@ function resolveRequirements(input) {
10828
11000
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
10829
11001
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
10830
11002
  before: "dependent_action",
10831
- dependentAction: "dependent-action",
11003
+ dependentAction: null,
10832
11004
  acceptanceAllowed: false
10833
11005
  }));
10834
11006
  if (signals.behaviorChange.value === true)
@@ -10986,59 +11158,6 @@ function diffPolicy(previous, next, reason, now) {
10986
11158
  return parsed.data;
10987
11159
  }
10988
11160
 
10989
- // packages/workit-core/src/core/workers.ts
10990
- var same = (left, right) => {
10991
- try {
10992
- return canonicalJson(left) === canonicalJson(right);
10993
- } catch {
10994
- return false;
10995
- }
10996
- };
10997
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
10998
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
10999
- const code = char.charCodeAt(0);
11000
- return code < 32 || code === 127;
11001
- }) && !value.split(/[\\/]/).includes("..");
11002
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
11003
- var workerIdOf = (value) => value.workerId ?? null;
11004
- function assertProductWriteAllowed(input) {
11005
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
11006
- return failure("invalid_input", "invalid product write path");
11007
- if (!input.store)
11008
- return failure("permission_denied", "write authorization requires core state");
11009
- const task = input.store.readTask(input.task.id);
11010
- if (!task.ok)
11011
- return task;
11012
- const workspace = input.store.readWorkspace();
11013
- if (!workspace.ok)
11014
- return workspace;
11015
- if (!workspace.data)
11016
- return failure("not_found", "workspace not found");
11017
- const workspaceRecord = workspace.data;
11018
- if (task.data.status !== "active")
11019
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
11020
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
11021
- return failure("recovery_required", "task and workspace bindings are invalid");
11022
- const writer = workspaceRecord.writer;
11023
- if (!writer)
11024
- return failure("permission_denied", "checkout has no writer owner");
11025
- if (writer.state === "uncertain")
11026
- return failure("recovery_required", "checkout writer requires recovery", {
11027
- owner: writer.owner
11028
- });
11029
- const caller = callerValue(input.caller);
11030
- const workerId = workerIdOf(input.caller);
11031
- const owner = writer.owner;
11032
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
11033
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
11034
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
11035
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
11036
- return failure("permission_denied", "only implementers can own product writes");
11037
- 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))))
11038
- return failure("recovery_required", "worker session is not an active writer");
11039
- return success(null, workspaceRecord.revision, owner);
11040
- }
11041
-
11042
11161
  // packages/workit-core/src/core/task-engine.ts
11043
11162
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
11044
11163
  kind,
@@ -11169,15 +11288,20 @@ var applyWorkerLifecycle = (input) => {
11169
11288
  return failure("invalid_transition", "worker has not started");
11170
11289
  if (input.state === "running" && entry.data.state === "stopped")
11171
11290
  return failure("invalid_transition", "stopped worker cannot run again");
11291
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
11172
11292
  const nextEntry = {
11173
11293
  ...entry,
11174
11294
  recordedAt: input.now ?? entry.recordedAt,
11175
11295
  provenance: authority.provenance,
11176
- data: { ...entry.data, state: input.state, session: input.session }
11296
+ data: {
11297
+ ...entry.data,
11298
+ state: terminalCancel ? "cancelling" : input.state,
11299
+ session: input.session
11300
+ }
11177
11301
  };
11178
11302
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11179
11303
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11180
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11304
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11181
11305
  return success(task.data.revision, workspace.data.revision, entry);
11182
11306
  const changed = input.store.mutateTaskAndWorkspace({
11183
11307
  taskId: task.data.id,
@@ -11547,7 +11671,7 @@ class WorkitCore {
11547
11671
  activeWorkerBlocker(task, workspace) {
11548
11672
  if (workspace.writer)
11549
11673
  return failure("recovery_required", "writer ownership must be released first");
11550
- if (task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
11674
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11551
11675
  return failure("recovery_required", "worker state requires reconciliation");
11552
11676
  return null;
11553
11677
  }
@@ -11784,11 +11908,6 @@ class WorkitCore {
11784
11908
  if (helper && !helper.ok)
11785
11909
  return helper;
11786
11910
  const evidence = input.evidence;
11787
- if (helper?.ok) {
11788
- const assignment = helper.data.data.assignment;
11789
- 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))
11790
- return failure("permission_denied", "evidence is outside the worker assignment");
11791
- }
11792
11911
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
11793
11912
  return failure("invalid_input", "missing or skipped evidence requires a reason");
11794
11913
  if (evidence.kind === "review") {
@@ -11805,6 +11924,11 @@ class WorkitCore {
11805
11924
  evidence.beforeCandidateId ??= null;
11806
11925
  evidence.candidateId ??= null;
11807
11926
  }
11927
+ if (helper?.ok) {
11928
+ const assignment = helper.data.data.assignment;
11929
+ 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))
11930
+ return failure("permission_denied", "evidence is outside the worker assignment");
11931
+ }
11808
11932
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11809
11933
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
11810
11934
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -11858,7 +11982,12 @@ class WorkitCore {
11858
11982
  const input = parsed.data;
11859
11983
  if (nativeRequired && input.action !== "record")
11860
11984
  return failure("permission_denied", "native observation is only valid for recording decisions");
11861
- if (nativeRequired && nativeObservation === undefined)
11985
+ const stated = input.action === "record" && input.response === "stated";
11986
+ if (stated && input.purpose === "action")
11987
+ return failure("permission_denied", "stated choices cannot authorize mutating actions");
11988
+ if (stated && !input.binding?.statedChoice)
11989
+ return failure("invalid_input", "stated choices require binding.statedChoice");
11990
+ if (nativeRequired && nativeObservation === undefined && !stated)
11862
11991
  return failure("permission_denied", "native decision observation is required");
11863
11992
  const task = this.store.readTask(input.taskId);
11864
11993
  if (!task.ok)
@@ -11891,7 +12020,7 @@ class WorkitCore {
11891
12020
  consumption: null
11892
12021
  };
11893
12022
  const data = { ...base, digest: decisionDigest(base) };
11894
- const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
12023
+ const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
11895
12024
  observation: nativeObservation,
11896
12025
  expected: {
11897
12026
  taskId: task.data.id,
@@ -12700,6 +12829,9 @@ class WorkitCore {
12700
12829
  id: task.id,
12701
12830
  revision: task.revision,
12702
12831
  workspaceRevision: workspace.data.revision,
12832
+ createdAt: task.createdAt,
12833
+ updatedAt: task.updatedAt,
12834
+ runtime: task.runtime ?? null,
12703
12835
  objective: task.intent.data.objective,
12704
12836
  status: task.status,
12705
12837
  closure: task.closure,
@@ -12785,7 +12917,7 @@ class WorkitCore {
12785
12917
  const base = reconcileResume(effectiveView);
12786
12918
  if (!base.ok)
12787
12919
  return base;
12788
- const needsReconciliation = view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
12920
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
12789
12921
  const blockers = [...base.data.blockers];
12790
12922
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
12791
12923
  blockers.push({
@@ -12852,7 +12984,8 @@ class WorkitCore {
12852
12984
  ...current,
12853
12985
  status,
12854
12986
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
12855
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
12987
+ progress: current.progress,
12988
+ pauseReason: status === "paused" ? input.reason ?? null : null
12856
12989
  })
12857
12990
  });
12858
12991
  if (!changed.ok)
@@ -12888,7 +13021,7 @@ class WorkitCore {
12888
13021
  return workspace;
12889
13022
  if (!workspace.data)
12890
13023
  return failure("not_found", "workspace not found");
12891
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
13024
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
12892
13025
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
12893
13026
  this.fillRevisions(input, task.data, workspace.data);
12894
13027
  const changed = this.store.mutateTaskAndWorkspace({
@@ -13017,6 +13150,13 @@ var cursorCapabilities = (availability = {}) => {
13017
13150
  reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
13018
13151
  refs: [hostRef("subagentStart")]
13019
13152
  },
13153
+ {
13154
+ name: "fresh-context-review",
13155
+ surface: "subagentStart",
13156
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
13157
+ 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",
13158
+ refs: [hostRef("subagentStart")]
13159
+ },
13020
13160
  {
13021
13161
  name: "arbitrary_shell_write",
13022
13162
  surface: "unobservable_shell",
@@ -13050,7 +13190,7 @@ var parseCursorHookInput = (value) => {
13050
13190
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
13051
13191
  return { ok: false, error: "exactly one workspace root is required" };
13052
13192
  const root = roots[0];
13053
- if (!path6.isAbsolute(root) || !existsSync3(root))
13193
+ if (!path7.isAbsolute(root) || !existsSync4(root))
13054
13194
  return { ok: false, error: "workspace root must be an existing absolute path" };
13055
13195
  const event = value.hook_event_name;
13056
13196
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;