@brainervirus/workit-cursor 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +7573,7 @@ var decisionSchema = object({
7573
7573
  scope: scopeSchema,
7574
7574
  presented: text,
7575
7575
  approvedContent: text,
7576
+ displayed: text.optional(),
7576
7577
  contentRefs: array(refSchema)
7577
7578
  }).strict(),
7578
7579
  digest,
@@ -7611,7 +7612,7 @@ var workerReportSchema = object({
7611
7612
  }).strict();
7612
7613
  var workerSchema = object({
7613
7614
  assignment: assignmentSchema,
7614
- state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
7615
+ state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7615
7616
  session: refSchema.nullable(),
7616
7617
  report: workerReportSchema.nullable()
7617
7618
  }).strict();
@@ -7651,6 +7652,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
7651
7652
  path: ["decisionId"]
7652
7653
  });
7653
7654
  });
7655
+ var runtimeSchema = object({
7656
+ createdWith: text.nullable(),
7657
+ updatedWith: text
7658
+ }).strict();
7654
7659
  var taskRecordSchema = object({
7655
7660
  schemaVersion: literal(1),
7656
7661
  id,
@@ -7664,6 +7669,8 @@ var taskRecordSchema = object({
7664
7669
  status: _enum(["active", "paused", "closed"]),
7665
7670
  closure: closureSchema.nullable(),
7666
7671
  progress: progressSchema,
7672
+ pauseReason: text.nullable().optional(),
7673
+ runtime: runtimeSchema.optional(),
7667
7674
  assessments: array(entrySchema(assessmentSchema)),
7668
7675
  policy: policySchema.nullable(),
7669
7676
  policyChanges: array(policyChangeSchema),
@@ -7679,6 +7686,7 @@ var workspaceRecordSchema = object({
7679
7686
  id,
7680
7687
  revision,
7681
7688
  root: nonEmpty,
7689
+ runtime: runtimeSchema.optional(),
7682
7690
  writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
7683
7691
  }).strict();
7684
7692
  var capabilitySchema = object({
@@ -7706,6 +7714,9 @@ var taskSummarySchema = object({
7706
7714
  id,
7707
7715
  revision,
7708
7716
  workspaceRevision: revision,
7717
+ createdAt: utc,
7718
+ updatedAt: utc,
7719
+ runtime: runtimeSchema.nullable(),
7709
7720
  objective: text,
7710
7721
  status: _enum(["active", "paused", "closed"]),
7711
7722
  closure: closureSchema.nullable(),
@@ -7776,7 +7787,7 @@ var taskOperations = {
7776
7787
  expectedWorkspaceRevision: revision.optional(),
7777
7788
  outcome: outcomeSchema,
7778
7789
  summary: text,
7779
- decisionIds: array(id)
7790
+ decisionIds: array(id).optional()
7780
7791
  })
7781
7792
  };
7782
7793
  var policyOperations = {
@@ -7868,14 +7879,15 @@ var writerOperations = {
7868
7879
  ...taskId,
7869
7880
  expectedRevision: revision.optional(),
7870
7881
  expectedWorkspaceRevision: revision.optional(),
7871
- workerId: nullableId.optional()
7882
+ workerId: nullableId.optional(),
7883
+ reason: text.optional()
7872
7884
  }),
7873
7885
  release: operation({
7874
7886
  action: literal("release"),
7875
7887
  ...taskId,
7876
7888
  expectedRevision: revision.optional(),
7877
7889
  expectedWorkspaceRevision: revision.optional(),
7878
- reason: text
7890
+ reason: text.optional()
7879
7891
  })
7880
7892
  };
7881
7893
  var stateOperations = {
@@ -8026,7 +8038,21 @@ function requirementId(input) {
8026
8038
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8027
8039
  import * as fs4 from "node:fs";
8028
8040
  import { hostname } from "node:os";
8029
- import path3 from "node:path";
8041
+ import path4 from "node:path";
8042
+
8043
+ // packages/workit-core/src/core/package-root.ts
8044
+ import { existsSync } from "node:fs";
8045
+ import path from "node:path";
8046
+ import { fileURLToPath } from "node:url";
8047
+ var packageRoot = () => {
8048
+ let dir = path.dirname(fileURLToPath(import.meta.url));
8049
+ while (true) {
8050
+ const parent = path.dirname(dir);
8051
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
8052
+ return dir;
8053
+ dir = parent;
8054
+ }
8055
+ };
8030
8056
 
8031
8057
  // node_modules/@openclaw/fs-safe/dist/errors.js
8032
8058
  var OPERATIONAL_CODES = new Set([
@@ -8077,7 +8103,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
8077
8103
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
8078
8104
  import { randomBytes } from "node:crypto";
8079
8105
  import fsSync from "node:fs";
8080
- import path from "node:path";
8106
+ import path2 from "node:path";
8081
8107
 
8082
8108
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
8083
8109
  import fs from "node:fs";
@@ -8167,13 +8193,13 @@ ${ownershipToken}
8167
8193
  return { raw, ownershipToken };
8168
8194
  }
8169
8195
  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)) {
8196
+ const resolved = path2.resolve(lockPath);
8197
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
8198
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
8199
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
8174
8200
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
8175
8201
  }
8176
- return relative.split(path.sep).join(path.posix.sep);
8202
+ return relative.split(path2.sep).join(path2.posix.sep);
8177
8203
  }
8178
8204
  function parseSidecarLockPayload(raw, parser) {
8179
8205
  if (parser) {
@@ -8320,7 +8346,7 @@ function getFsSafeLockConfig() {
8320
8346
 
8321
8347
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
8322
8348
  import fs3 from "node:fs";
8323
- import path2 from "node:path";
8349
+ import path3 from "node:path";
8324
8350
 
8325
8351
  // node_modules/@openclaw/fs-safe/dist/timing.js
8326
8352
  function sleepSync(ms) {
@@ -8411,26 +8437,26 @@ function canonicalLockParentSync(parent) {
8411
8437
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
8412
8438
  }
8413
8439
  function normalizeTargetPath(targetPath) {
8414
- const resolved = path2.resolve(targetPath);
8415
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
8440
+ const resolved = path3.resolve(targetPath);
8441
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
8416
8442
  try {
8417
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
8443
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
8418
8444
  } catch {
8419
8445
  return resolved;
8420
8446
  }
8421
8447
  }
8422
8448
  function boundedLockPath(lockPath, lockRoot) {
8423
- const resolved = path2.resolve(lockPath);
8449
+ const resolved = path3.resolve(lockPath);
8424
8450
  if (!lockRoot)
8425
8451
  return resolved;
8426
8452
  relativeSidecarLockPath(lockRoot, resolved);
8427
- const parent = path2.dirname(resolved);
8453
+ const parent = path3.dirname(resolved);
8428
8454
  const parentReal = canonicalLockParentSync(parent);
8429
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
8430
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
8455
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
8456
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
8431
8457
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
8432
8458
  }
8433
- return path2.join(parentReal, path2.basename(resolved));
8459
+ return path3.join(parentReal, path3.basename(resolved));
8434
8460
  }
8435
8461
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
8436
8462
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -8643,6 +8669,33 @@ var metadataLockSchema = object({
8643
8669
  nonce: string2().min(1)
8644
8670
  }).strict();
8645
8671
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
8672
+ var cachedRuntimeVersion = null;
8673
+ var runtimeVersion = () => {
8674
+ if (cachedRuntimeVersion === null) {
8675
+ try {
8676
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
8677
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
8678
+ } catch {
8679
+ cachedRuntimeVersion = "0.0.0";
8680
+ }
8681
+ }
8682
+ return cachedRuntimeVersion;
8683
+ };
8684
+ var stampNew = (record) => ({
8685
+ createdWith: record.runtime?.createdWith ?? null,
8686
+ updatedWith: runtimeVersion()
8687
+ });
8688
+ var isNewerVersion = (candidate, current) => {
8689
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
8690
+ const [next, now] = [parts(candidate), parts(current)];
8691
+ for (let index = 0;index < 3; index += 1) {
8692
+ const left = next[index] ?? 0;
8693
+ const right = now[index] ?? 0;
8694
+ if (left !== right)
8695
+ return left > right;
8696
+ }
8697
+ return false;
8698
+ };
8646
8699
  var jsonBytes = (value) => `${canonicalJson(value)}
8647
8700
  `;
8648
8701
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -8669,12 +8722,12 @@ var sameMetadataLock = (left, right) => {
8669
8722
  class TaskStore {
8670
8723
  root;
8671
8724
  constructor(root) {
8672
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
8725
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
8673
8726
  }
8674
8727
  readTask(taskId) {
8675
8728
  if (!validId(taskId))
8676
8729
  return failure("invalid_input", "task ID is invalid", { taskId });
8677
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8730
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8678
8731
  if (!result.exists)
8679
8732
  return failure("not_found", "task not found", { taskId });
8680
8733
  if (result.result.ok && result.result.data.id !== taskId)
@@ -8708,7 +8761,7 @@ class TaskStore {
8708
8761
  });
8709
8762
  const tasks = [];
8710
8763
  for (const name of names.sort()) {
8711
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
8764
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
8712
8765
  if (!item.exists)
8713
8766
  continue;
8714
8767
  if (!item.result.ok)
@@ -8742,7 +8795,7 @@ class TaskStore {
8742
8795
  if (!current.ok)
8743
8796
  return current;
8744
8797
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
8745
- return failure("revision_conflict", "workspace revision does not match", {
8798
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8746
8799
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
8747
8800
  actualWorkspaceRevision: current.data?.revision ?? null
8748
8801
  });
@@ -8754,11 +8807,17 @@ class TaskStore {
8754
8807
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
8755
8808
  if (current.data && !previousWorkspaceBytes)
8756
8809
  return failure("storage_error", "workspace snapshot disappeared during creation");
8757
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8810
+ const workspace = current.data ? {
8811
+ ...current.data,
8812
+ revision: newRevision(),
8813
+ root: this.root,
8814
+ runtime: stampNew(current.data)
8815
+ } : {
8758
8816
  schemaVersion: SCHEMA_VERSION,
8759
8817
  id: newId(),
8760
8818
  revision: newRevision(),
8761
8819
  root: this.root,
8820
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8762
8821
  writer: null
8763
8822
  };
8764
8823
  const timestamp = value.now ?? now();
@@ -8780,6 +8839,7 @@ class TaskStore {
8780
8839
  status: "active",
8781
8840
  closure: null,
8782
8841
  progress: { summary: "", nextAction: null, blockers: [] },
8842
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8783
8843
  assessments: [],
8784
8844
  policy: null,
8785
8845
  policyChanges: [],
@@ -8810,7 +8870,7 @@ class TaskStore {
8810
8870
  if (!current.ok)
8811
8871
  return current;
8812
8872
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
8813
- return failure("revision_conflict", "workspace revision does not match", {
8873
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8814
8874
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
8815
8875
  actualWorkspaceRevision: current.data?.revision ?? null
8816
8876
  });
@@ -8818,11 +8878,17 @@ class TaskStore {
8818
8878
  if (current.data && !previousWorkspaceBytes)
8819
8879
  return failure("storage_error", "workspace snapshot disappeared during import");
8820
8880
  const timestamp = input.now ?? now();
8821
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8881
+ const workspace = current.data ? {
8882
+ ...current.data,
8883
+ revision: newRevision(),
8884
+ root: this.root,
8885
+ runtime: stampNew(current.data)
8886
+ } : {
8822
8887
  schemaVersion: SCHEMA_VERSION,
8823
8888
  id: input.workspaceId ?? newId(),
8824
8889
  revision: newRevision(),
8825
8890
  root: this.root,
8891
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8826
8892
  writer: null
8827
8893
  };
8828
8894
  const task = {
@@ -8830,7 +8896,11 @@ class TaskStore {
8830
8896
  workspaceId: workspace.id,
8831
8897
  revision: newRevision(),
8832
8898
  createdAt: timestamp,
8833
- updatedAt: timestamp
8899
+ updatedAt: timestamp,
8900
+ runtime: {
8901
+ createdWith: input.task.runtime?.createdWith ?? null,
8902
+ updatedWith: runtimeVersion()
8903
+ }
8834
8904
  };
8835
8905
  const validTask = taskRecordSchema.safeParse(task);
8836
8906
  if (!validTask.success)
@@ -8879,7 +8949,8 @@ class TaskStore {
8879
8949
  workspaceId: current.data.workspaceId,
8880
8950
  createdAt: current.data.createdAt,
8881
8951
  revision: context.revision,
8882
- updatedAt: context.now
8952
+ updatedAt: context.now,
8953
+ runtime: stampNew(current.data)
8883
8954
  };
8884
8955
  const valid = taskRecordSchema.safeParse(record);
8885
8956
  if (!valid.success)
@@ -8913,7 +8984,8 @@ class TaskStore {
8913
8984
  ...changed.data,
8914
8985
  id: current.data.id,
8915
8986
  root: this.root,
8916
- revision: context.revision
8987
+ revision: context.revision,
8988
+ runtime: stampNew(current.data)
8917
8989
  };
8918
8990
  const valid = workspaceRecordSchema.safeParse(record);
8919
8991
  if (!valid.success)
@@ -8956,7 +9028,8 @@ class TaskStore {
8956
9028
  ...changedWorkspace.data,
8957
9029
  id: workspace.data.id,
8958
9030
  root: this.root,
8959
- revision: workspaceContext.revision
9031
+ revision: workspaceContext.revision,
9032
+ runtime: stampNew(workspace.data)
8960
9033
  });
8961
9034
  if (!nextWorkspace.success)
8962
9035
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -8981,7 +9054,8 @@ class TaskStore {
8981
9054
  workspaceId: task.data.workspaceId,
8982
9055
  createdAt: task.data.createdAt,
8983
9056
  revision: taskContext.revision,
8984
- updatedAt: taskContext.now
9057
+ updatedAt: taskContext.now,
9058
+ runtime: stampNew(task.data)
8985
9059
  });
8986
9060
  if (!nextTask.success) {
8987
9061
  this.markUncertain(nextWorkspace.data);
@@ -9008,7 +9082,7 @@ class TaskStore {
9008
9082
  if (match)
9009
9083
  candidates.push({
9010
9084
  target: match[1],
9011
- path: path3.join(this.recoveryDir, name),
9085
+ path: path4.join(this.recoveryDir, name),
9012
9086
  digest: match[3]
9013
9087
  });
9014
9088
  }
@@ -9201,7 +9275,8 @@ class TaskStore {
9201
9275
  const value = {
9202
9276
  ...workspace,
9203
9277
  writer: { ...workspace.writer, state: "uncertain" },
9204
- revision: newRevision()
9278
+ revision: newRevision(),
9279
+ runtime: stampNew(workspace)
9205
9280
  };
9206
9281
  this.replaceSnapshot(this.workspacePath, value, workspace);
9207
9282
  }
@@ -9279,7 +9354,7 @@ class TaskStore {
9279
9354
  }
9280
9355
  fs4.renameSync(temporary, file);
9281
9356
  temporary = undefined;
9282
- this.fsyncDirectory(path3.dirname(file));
9357
+ this.fsyncDirectory(path4.dirname(file));
9283
9358
  return success(null, null, value);
9284
9359
  } catch (error) {
9285
9360
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -9293,9 +9368,9 @@ class TaskStore {
9293
9368
  }
9294
9369
  }
9295
9370
  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`);
9371
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
9372
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
9373
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9299
9374
  if (fs4.existsSync(destination)) {
9300
9375
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
9301
9376
  return;
@@ -9373,7 +9448,7 @@ class TaskStore {
9373
9448
  for (const name of fs4.readdirSync(this.tasksDir)) {
9374
9449
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
9375
9450
  return null;
9376
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
9451
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
9377
9452
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
9378
9453
  return null;
9379
9454
  ids.add(parsed.data.workspaceId);
@@ -9415,7 +9490,12 @@ class TaskStore {
9415
9490
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
9416
9491
  return failure("unsupported_version", "unsupported snapshot schema version");
9417
9492
  const parsed = schema.safeParse(value);
9418
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
9493
+ if (parsed.success)
9494
+ return success(null, null, parsed.data);
9495
+ const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
9496
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
9497
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
9498
+ return failure("recovery_required", "snapshot does not satisfy its schema");
9419
9499
  }
9420
9500
  processStart(pid) {
9421
9501
  try {
@@ -9425,43 +9505,98 @@ class TaskStore {
9425
9505
  }
9426
9506
  }
9427
9507
  conflict(expected, actual) {
9428
- return failure("revision_conflict", "snapshot revision does not match", {
9508
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
9429
9509
  expectedRevision: expected,
9430
9510
  actualRevision: actual
9431
9511
  });
9432
9512
  }
9433
9513
  taskPath(taskId) {
9434
- return path3.join(this.tasksDir, `${taskId}.json`);
9514
+ return path4.join(this.tasksDir, `${taskId}.json`);
9435
9515
  }
9436
9516
  get workitDir() {
9437
- return path3.join(this.root, ".workit");
9517
+ return path4.join(this.root, ".workit");
9438
9518
  }
9439
9519
  get tasksDir() {
9440
- return path3.join(this.workitDir, "tasks");
9520
+ return path4.join(this.workitDir, "tasks");
9441
9521
  }
9442
9522
  get recoveryDir() {
9443
- return path3.join(this.workitDir, "recovery");
9523
+ return path4.join(this.workitDir, "recovery");
9444
9524
  }
9445
9525
  get workspacePath() {
9446
- return path3.join(this.workitDir, "workspace.json");
9526
+ return path4.join(this.workitDir, "workspace.json");
9447
9527
  }
9448
9528
  get lockPath() {
9449
- return path3.join(this.workitDir, "metadata.lock");
9529
+ return path4.join(this.workitDir, "metadata.lock");
9450
9530
  }
9451
9531
  get gitignorePath() {
9452
- return path3.join(this.workitDir, ".gitignore");
9532
+ return path4.join(this.workitDir, ".gitignore");
9453
9533
  }
9454
9534
  }
9535
+ // packages/workit-core/src/core/workers.ts
9536
+ var same = (left, right) => {
9537
+ try {
9538
+ return canonicalJson(left) === canonicalJson(right);
9539
+ } catch {
9540
+ return false;
9541
+ }
9542
+ };
9543
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9544
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
9545
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
9546
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9547
+ const code = char.charCodeAt(0);
9548
+ return code < 32 || code === 127;
9549
+ }) && !value.split(/[\\/]/).includes("..");
9550
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
9551
+ var workerIdOf = (value) => value.workerId ?? null;
9552
+ function assertProductWriteAllowed(input) {
9553
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9554
+ return failure("invalid_input", "invalid product write path");
9555
+ if (!input.store)
9556
+ return failure("permission_denied", "write authorization requires core state");
9557
+ const task = input.store.readTask(input.task.id);
9558
+ if (!task.ok)
9559
+ return task;
9560
+ const workspace = input.store.readWorkspace();
9561
+ if (!workspace.ok)
9562
+ return workspace;
9563
+ if (!workspace.data)
9564
+ return failure("not_found", "workspace not found");
9565
+ const workspaceRecord = workspace.data;
9566
+ if (task.data.status !== "active")
9567
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
9568
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
9569
+ return failure("recovery_required", "task and workspace bindings are invalid");
9570
+ const writer = workspaceRecord.writer;
9571
+ if (!writer)
9572
+ return failure("permission_denied", "checkout has no writer owner");
9573
+ if (writer.state === "uncertain")
9574
+ return failure("recovery_required", "checkout writer requires recovery", {
9575
+ owner: writer.owner
9576
+ });
9577
+ const caller = callerValue(input.caller);
9578
+ const workerId = workerIdOf(input.caller);
9579
+ const owner = writer.owner;
9580
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
9581
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
9582
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
9583
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
9584
+ return failure("permission_denied", "only implementers can own product writes");
9585
+ 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))))
9586
+ return failure("recovery_required", "worker session is not an active writer");
9587
+ return success(null, workspaceRecord.revision, owner);
9588
+ }
9589
+
9455
9590
  // packages/workit-core/src/core/task-evaluation.ts
9456
9591
  import { createHash as createHash4 } from "node:crypto";
9457
9592
  import * as fs6 from "node:fs";
9458
9593
  import { spawnSync } from "node:child_process";
9459
- import path5 from "node:path";
9594
+ import path6 from "node:path";
9460
9595
 
9461
9596
  // packages/workit-core/src/core/authority.ts
9462
9597
  import { createHash as createHash3 } from "node:crypto";
9463
9598
  import * as fs5 from "node:fs";
9464
- import path4 from "node:path";
9599
+ import path5 from "node:path";
9465
9600
  var verifiedAuthorities = new WeakMap;
9466
9601
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
9467
9602
  const token = {};
@@ -9588,20 +9723,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
9588
9723
  continue;
9589
9724
  if (!reference.digest)
9590
9725
  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");
9726
+ const target = path5.resolve(root, reference.path);
9727
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
9728
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9729
+ fields: [
9730
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
9731
+ ]
9732
+ });
9594
9733
  try {
9595
- const relative = path4.relative(root, target);
9734
+ const relative = path5.relative(root, target);
9596
9735
  let current = root;
9597
- for (const segment of relative.split(path4.sep)) {
9736
+ for (const segment of relative.split(path5.sep)) {
9598
9737
  if (!segment)
9599
9738
  continue;
9600
- current = path4.join(current, segment);
9739
+ current = path5.join(current, segment);
9601
9740
  const stat = fs5.lstatSync(current);
9602
9741
  const real = fs5.realpathSync(current);
9603
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
9604
- return failure("invalid_input", "document reference escapes checkout");
9742
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
9743
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9744
+ fields: [
9745
+ {
9746
+ path: "contentRefs.path",
9747
+ reason: `${reference.path} resolves outside the checkout`
9748
+ }
9749
+ ]
9750
+ });
9605
9751
  if (stat.isSymbolicLink())
9606
9752
  return failure("invalid_input", "document reference uses a symlink");
9607
9753
  if (current === target && !stat.isFile())
@@ -9948,14 +10094,14 @@ var verifyDecisionContent = verifyContentRefs;
9948
10094
 
9949
10095
  // packages/workit-core/src/core/task-evaluation.ts
9950
10096
  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("/") || ".";
10097
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
10098
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
9953
10099
  var canonicalPath = (value) => {
9954
10100
  if (typeof value !== "string" || !value)
9955
10101
  return null;
9956
10102
  if (value.includes("\\") || value.split("/").includes(".."))
9957
10103
  return null;
9958
- const normalized = path5.posix.normalize(value);
10104
+ const normalized = path6.posix.normalize(value);
9959
10105
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
9960
10106
  return null;
9961
10107
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10003,14 +10149,14 @@ var gitPaths = (root) => {
10003
10149
  git: /not a git repository/i.test(stderr) ? false : true
10004
10150
  };
10005
10151
  }
10006
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
10152
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
10007
10153
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10008
10154
  if (stagedDeleted.status !== 0) {
10009
10155
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10010
10156
  if (!/not a git repository/i.test(stderr))
10011
10157
  return { paths, uncertain: true, git: true };
10012
10158
  } else if (stagedDeleted.stdout) {
10013
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
10159
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
10014
10160
  }
10015
10161
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10016
10162
  };
@@ -10025,7 +10171,7 @@ var walk = (root, directory, output) => {
10025
10171
  for (const name of names) {
10026
10172
  if (name === ".git" || name === ".workit")
10027
10173
  continue;
10028
- const target = path5.join(directory, name);
10174
+ const target = path6.join(directory, name);
10029
10175
  let stat;
10030
10176
  try {
10031
10177
  stat = fs6.lstatSync(target);
@@ -10043,17 +10189,17 @@ var walk = (root, directory, output) => {
10043
10189
  var scopeRoots = (root, scope) => {
10044
10190
  const roots = [];
10045
10191
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10046
- const target = path5.resolve(root, item);
10192
+ const target = path6.resolve(root, item);
10047
10193
  if (!inside(root, target))
10048
- return failure("invalid_input", "candidate scope escapes checkout");
10194
+ 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
10195
  let ancestor = target;
10050
10196
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10051
- ancestor = path5.dirname(ancestor);
10197
+ ancestor = path6.dirname(ancestor);
10052
10198
  try {
10053
10199
  if (!inside(root, fs6.realpathSync(ancestor)))
10054
- return failure("invalid_input", "candidate scope escapes checkout");
10200
+ 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
10201
  } catch {
10056
- return failure("invalid_input", "candidate scope cannot be inspected");
10202
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
10057
10203
  }
10058
10204
  roots.push(target);
10059
10205
  }
@@ -10068,7 +10214,7 @@ function captureCandidate(root, scope, environment = []) {
10068
10214
  }
10069
10215
  const normalizedScope = canonicalScope(scope);
10070
10216
  if (!normalizedScope)
10071
- return failure("invalid_input", "candidate scope is invalid");
10217
+ 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
10218
  const roots = scopeRoots(checkout, normalizedScope);
10073
10219
  if (!roots.ok)
10074
10220
  return roots;
@@ -10100,7 +10246,7 @@ function captureCandidate(root, scope, environment = []) {
10100
10246
  for (const item of [...names].sort()) {
10101
10247
  if (!scopeMatches(item, normalizedScope))
10102
10248
  continue;
10103
- const target = path5.join(checkout, item);
10249
+ const target = path6.join(checkout, item);
10104
10250
  try {
10105
10251
  const stat = fs6.lstatSync(target);
10106
10252
  if (stat.isSymbolicLink()) {
@@ -10257,7 +10403,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10257
10403
  return true;
10258
10404
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10259
10405
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10260
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
10406
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10261
10407
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10262
10408
  });
10263
10409
  if (passed.length) {
@@ -10353,7 +10499,17 @@ function evaluateClosure(requestedOutcome, view) {
10353
10499
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10354
10500
  if (requestedOutcome !== "stopped" && blocking.length)
10355
10501
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10356
- requirementIds: blocking.map((item) => item.requirementId)
10502
+ requirementIds: blocking.map((item) => item.requirementId),
10503
+ requirements: blocking.map((item) => {
10504
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10505
+ return {
10506
+ requirementId: item.requirementId,
10507
+ ruleId: requirement?.ruleId ?? "unknown",
10508
+ reason: item.reason,
10509
+ satisfaction: requirement?.satisfaction ?? "",
10510
+ dependentAction: requirement?.dependentAction ?? null
10511
+ };
10512
+ })
10357
10513
  });
10358
10514
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
10359
10515
  if (requestedOutcome === "verified" && accepted.length)
@@ -10380,6 +10536,106 @@ function evaluateClosure(requestedOutcome, view) {
10380
10536
  });
10381
10537
  }
10382
10538
 
10539
+ // packages/workit-core/src/core/methods.ts
10540
+ var METHODS = {
10541
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
10542
+ "workit-behavioral-tdd": { dimensions: ["testing"] },
10543
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
10544
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
10545
+ "workit-implement": { dimensions: ["delegation"] },
10546
+ "workit-debug": { ruleIds: ["root-cause-investigation"] },
10547
+ "workit-handoff": { ruleIds: ["durable-handoff"] },
10548
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
10549
+ };
10550
+ var methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true;
10551
+ var assuranceFor = (id, definition, capabilities) => {
10552
+ const keys = new Set([
10553
+ id,
10554
+ ...definition.dimensions ?? [],
10555
+ ...definition.ruleIds ?? []
10556
+ ]);
10557
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
10558
+ if (id === "workit-review" && matches.length === 0)
10559
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
10560
+ if (matches.some((capability) => capability.assurance === "unavailable"))
10561
+ return {
10562
+ assurance: "unavailable",
10563
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
10564
+ };
10565
+ if (matches.some((capability) => capability.assurance === "enforced"))
10566
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
10567
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
10568
+ };
10569
+ function selectMethods(policy, capabilities) {
10570
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
10571
+ const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
10572
+ if (requirements.length === 0)
10573
+ return [];
10574
+ const capability = assuranceFor(id, definition, capabilities);
10575
+ const reasons = [
10576
+ ...new Set(requirements.map((requirement) => requirement.reason)),
10577
+ ...capability.reasons
10578
+ ];
10579
+ if (capability.assurance === "unavailable")
10580
+ reasons.unshift("required capability is unavailable");
10581
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
10582
+ });
10583
+ }
10584
+ var invariantBootstrap = () => `
10585
+ Workit keeps one accountable lead and one shared task state. Inspect current task
10586
+ state before acting. An empty task.list means no session yet, not permission to
10587
+ skip Workit; for user-requested product, debug, or behavior work with no active
10588
+ or paused task, run task.start then policy.assess before other product mutations.
10589
+ Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
10590
+ to the current records; explicit values are still concurrency-checked, so never
10591
+ copy revisions between calls. Happy path: task.list, task.start {intent} with no
10592
+ revisions, policy.assess {assessment: {facts, signals, consequences,
10593
+ verification}} where facts are inferred or observed with file refs,
10594
+ writer.acquire {taskId} before product writes, evidence.record {evidence} where
10595
+ check and review kinds auto-bind the current tree and need no digests. Record
10596
+ self-review requirements with kind review and reviewContext matching your own
10597
+ host session; the lead may also record checks. Fresh-context review requires a
10598
+ session distinct from the task creator and every other evidence recorder. Record
10599
+ verification evidence last: later evidence or commits can reopen fixed findings
10600
+ for re-verification, so verify, resolve, then close with nothing in between.
10601
+ decision.record {binding with taskId/workspaceId from inspect plus presented and
10602
+ approvedContent} only through a native approval question asked receipt-shaped:
10603
+ header Workit decision: <purpose>, the same label repeated in the question
10604
+ text (some host UIs do not render headers), exactly two options approved and
10605
+ rejected; ask once, never re-ask to mint a receipt. Before asking, present the
10606
+ item in the conversation (durable artifacts: complete digest plus exact path;
10607
+ inline plans: the content) and keep the question to one short scoped sentence;
10608
+ never ask to approve something unseen. An answer with custom text is steering,
10609
+ not approval: adjust, re-present, then ask a new scoped question. After the user
10610
+ approves a spec or plan, execute the remaining tasks continuously with one
10611
+ atomic commit per task and do not ask whether to continue; stop only for a new
10612
+ product decision, a failed safety or verification gate, a conflicting edit, or
10613
+ missing authority. Record the plan's commit list once through the plan-scoped
10614
+ action approval (git.commit with plan_steps and plan_branch) so each listed
10615
+ commit executes without a new question; an unlisted message needs a fresh
10616
+ approval. task.close {outcome,
10617
+ summary, decisionIds}. A completed native subagent run stops its bound worker
10618
+ by itself; if a worker strands in cancelling with its run verifiably over,
10619
+ repeat worker.cancel {reason} to confirm the stop. Check and review evidence
10620
+ without a bound candidate goes stale; findings close only as fixed with passing
10621
+ verification, dismissed with supporting evidence, or deferred under an approved
10622
+ limitation. Use only the
10623
+ shared operations for task, policy, evidence, finding, decision, worker, writer,
10624
+ and state changes. Authority is bounded by the requested scope, current
10625
+ revision, caller/session provenance, and observed capabilities. Never claim host
10626
+ enforcement or evidence that the host cannot provide. Shell-executed writes are
10627
+ unattested agent-guided work even when a writer is held. Preserve unresolved
10628
+ requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
10629
+ always load on demand. Model-invoked: new instructions, interruptions, or
10630
+ forgotten items mid-task → load workit-steer before responding; about to open
10631
+ a PR or fresh from implementing → load workit-deslop; CI red → load
10632
+ workit-green-run; small-looking change before close or merge → load
10633
+ workit-blast-radius; ambiguous, consequential, or disputed proposal before
10634
+ recommending → load workit-challenge; spec or plan needs a flow diagram →
10635
+ load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
10636
+ act from memory of it.
10637
+ `.trim();
10638
+
10383
10639
  // packages/workit-core/src/core/task-context.ts
10384
10640
  function reconcileResume(view, observations = []) {
10385
10641
  if (observations.length > 0)
@@ -10389,7 +10645,7 @@ function reconcileResume(view, observations = []) {
10389
10645
  return candidate;
10390
10646
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
10391
10647
  const blockers = [...view.task.progress.blockers];
10392
- if (view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
10648
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
10393
10649
  blockers.push({
10394
10650
  reason: "worker state requires reconciliation",
10395
10651
  dependentAction: "resume",
@@ -10439,6 +10695,11 @@ function compactTaskContext(view) {
10439
10695
  digest: entry.data.digest,
10440
10696
  references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
10441
10697
  }));
10698
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
10699
+ id: method.id,
10700
+ assurance: method.assurance,
10701
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
10702
+ })) : [];
10442
10703
  const gaps = Array.from(new Set([
10443
10704
  ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
10444
10705
  ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
@@ -10448,15 +10709,18 @@ function compactTaskContext(view) {
10448
10709
  objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
10449
10710
  status: view.task.status,
10450
10711
  decisions,
10712
+ methods,
10451
10713
  gaps,
10452
10714
  nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
10453
10715
  };
10454
10716
  let encoded = JSON.stringify(context);
10455
- while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.gaps.length)) {
10717
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
10456
10718
  if (context.gaps.length)
10457
10719
  context.gaps.pop();
10458
- else
10720
+ else if (context.decisions.length)
10459
10721
  context.decisions.pop();
10722
+ else
10723
+ context.methods.pop();
10460
10724
  encoded = JSON.stringify(context);
10461
10725
  }
10462
10726
  if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
@@ -10472,50 +10736,6 @@ var exportDigest = (bundle) => sha256({
10472
10736
  sourceWorkspaceId: bundle.sourceWorkspaceId,
10473
10737
  task: bundle.task
10474
10738
  });
10475
- // packages/workit-core/src/core/methods.ts
10476
- var invariantBootstrap = () => `
10477
- Workit keeps one accountable lead and one shared task state. Inspect current task
10478
- state before acting. An empty task.list means no session yet, not permission to
10479
- skip Workit; for user-requested product, debug, or behavior work with no active
10480
- or paused task, run task.start then policy.assess before other product mutations.
10481
- Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
10482
- to the current records; explicit values are still concurrency-checked, so never
10483
- copy revisions between calls. Happy path: task.list, task.start {intent} with no
10484
- revisions, policy.assess {assessment: {facts, signals, consequences,
10485
- verification}} where facts are inferred or observed with file refs,
10486
- writer.acquire {taskId} before product writes, evidence.record {evidence} where
10487
- check and review kinds auto-bind the current tree and need no digests. Record
10488
- self-review requirements with kind review and reviewContext matching your own
10489
- host session; the lead may also record checks. Fresh-context review requires a
10490
- session distinct from the task creator and every other evidence recorder. Record
10491
- verification evidence last: later evidence or commits can reopen fixed findings
10492
- for re-verification, so verify, resolve, then close with nothing in between.
10493
- decision.record {binding with taskId/workspaceId from inspect plus presented and
10494
- approvedContent} only through a native approval question asked receipt-shaped:
10495
- header Workit decision: <purpose>, the same label repeated in the question
10496
- text (some host UIs do not render headers), exactly two options approved and
10497
- rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
10498
- summary, decisionIds}. A completed native subagent run stops its bound worker
10499
- by itself; if a worker strands in cancelling with its run verifiably over,
10500
- repeat worker.cancel {reason} to confirm the stop. Check and review evidence
10501
- without a bound candidate goes stale; findings close only as fixed with passing
10502
- verification, dismissed with supporting evidence, or deferred under an approved
10503
- limitation. Use only the
10504
- shared operations for task, policy, evidence, finding, decision, worker, writer,
10505
- and state changes. Authority is bounded by the requested scope, current
10506
- revision, caller/session provenance, and observed capabilities. Never claim host
10507
- enforcement or evidence that the host cannot provide. Shell-executed writes are
10508
- unattested agent-guided work even when a writer is held. Preserve unresolved
10509
- requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
10510
- always load on demand. Model-invoked: new instructions, interruptions, or
10511
- forgotten items mid-task → load workit-steer before responding; about to open
10512
- a PR or fresh from implementing → load workit-deslop; CI red → load
10513
- workit-green-run; small-looking change before close or merge → load
10514
- workit-blast-radius; ambiguous, consequential, or disputed proposal before
10515
- recommending → load workit-challenge; spec or plan needs a flow diagram →
10516
- load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
10517
- act from memory of it.
10518
- `.trim();
10519
10739
  // packages/workit-core/src/core/external-action.ts
10520
10740
  var externalActionSchema = discriminatedUnion("operation", [
10521
10741
  object({
@@ -10529,7 +10749,11 @@ var externalActionSchema = discriminatedUnion("operation", [
10529
10749
  }).strict(),
10530
10750
  object({
10531
10751
  operation: literal("git.commit"),
10532
- payload: object({ message: string2().min(1) }).strict()
10752
+ payload: object({
10753
+ message: string2().min(1).optional(),
10754
+ plan_steps: array(string2().min(1)).optional(),
10755
+ plan_branch: string2().min(1).optional()
10756
+ }).strict()
10533
10757
  }).strict(),
10534
10758
  object({
10535
10759
  operation: literal("git.push"),
@@ -10764,7 +10988,7 @@ function resolveRequirements(input) {
10764
10988
  ].join(", ")}`,
10765
10989
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
10766
10990
  before: "dependent_action",
10767
- dependentAction: "dependent-action",
10991
+ dependentAction: null,
10768
10992
  acceptanceAllowed: false
10769
10993
  }));
10770
10994
  if (signals.productChoiceOpen.value === true)
@@ -10775,7 +10999,7 @@ function resolveRequirements(input) {
10775
10999
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
10776
11000
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
10777
11001
  before: "dependent_action",
10778
- dependentAction: "dependent-action",
11002
+ dependentAction: null,
10779
11003
  acceptanceAllowed: false
10780
11004
  }));
10781
11005
  if (signals.behaviorChange.value === true)
@@ -10933,59 +11157,6 @@ function diffPolicy(previous, next, reason, now) {
10933
11157
  return parsed.data;
10934
11158
  }
10935
11159
 
10936
- // packages/workit-core/src/core/workers.ts
10937
- var same = (left, right) => {
10938
- try {
10939
- return canonicalJson(left) === canonicalJson(right);
10940
- } catch {
10941
- return false;
10942
- }
10943
- };
10944
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
10945
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
10946
- const code = char.charCodeAt(0);
10947
- return code < 32 || code === 127;
10948
- }) && !value.split(/[\\/]/).includes("..");
10949
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
10950
- var workerIdOf = (value) => value.workerId ?? null;
10951
- function assertProductWriteAllowed(input) {
10952
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
10953
- return failure("invalid_input", "invalid product write path");
10954
- if (!input.store)
10955
- return failure("permission_denied", "write authorization requires core state");
10956
- const task = input.store.readTask(input.task.id);
10957
- if (!task.ok)
10958
- return task;
10959
- const workspace = input.store.readWorkspace();
10960
- if (!workspace.ok)
10961
- return workspace;
10962
- if (!workspace.data)
10963
- return failure("not_found", "workspace not found");
10964
- const workspaceRecord = workspace.data;
10965
- if (task.data.status !== "active")
10966
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
10967
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
10968
- return failure("recovery_required", "task and workspace bindings are invalid");
10969
- const writer = workspaceRecord.writer;
10970
- if (!writer)
10971
- return failure("permission_denied", "checkout has no writer owner");
10972
- if (writer.state === "uncertain")
10973
- return failure("recovery_required", "checkout writer requires recovery", {
10974
- owner: writer.owner
10975
- });
10976
- const caller = callerValue(input.caller);
10977
- const workerId = workerIdOf(input.caller);
10978
- const owner = writer.owner;
10979
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
10980
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
10981
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
10982
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
10983
- return failure("permission_denied", "only implementers can own product writes");
10984
- 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))))
10985
- return failure("recovery_required", "worker session is not an active writer");
10986
- return success(null, workspaceRecord.revision, owner);
10987
- }
10988
-
10989
11160
  // packages/workit-core/src/core/task-engine.ts
10990
11161
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
10991
11162
  kind,
@@ -11104,10 +11275,10 @@ var applyWorkerLifecycle = (input) => {
11104
11275
  const notStarted = input.dispatch === true;
11105
11276
  if (notStarted && (input.state !== "stopped" || input.session !== null))
11106
11277
  return failure("invalid_input", "a never-dispatched worker stops with no session");
11107
- if (notStarted && (entry.data.state === "stopped" || entry.data.state === "unknown"))
11108
- return failure("invalid_transition", "worker is no longer awaiting dispatch");
11109
- if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
11278
+ if (notStarted && entry.data.session !== null)
11110
11279
  return failure("invalid_transition", "worker already has an observed session");
11280
+ if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
11281
+ return failure("invalid_transition", "worker launch was not claimed");
11111
11282
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
11112
11283
  return failure("invalid_input", "running worker observations require a session");
11113
11284
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -11116,15 +11287,20 @@ var applyWorkerLifecycle = (input) => {
11116
11287
  return failure("invalid_transition", "worker has not started");
11117
11288
  if (input.state === "running" && entry.data.state === "stopped")
11118
11289
  return failure("invalid_transition", "stopped worker cannot run again");
11290
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
11119
11291
  const nextEntry = {
11120
11292
  ...entry,
11121
11293
  recordedAt: input.now ?? entry.recordedAt,
11122
11294
  provenance: authority.provenance,
11123
- data: { ...entry.data, state: input.state, session: input.session }
11295
+ data: {
11296
+ ...entry.data,
11297
+ state: terminalCancel ? "cancelling" : input.state,
11298
+ session: input.session
11299
+ }
11124
11300
  };
11125
11301
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11126
11302
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11127
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11303
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11128
11304
  return success(task.data.revision, workspace.data.revision, entry);
11129
11305
  const changed = input.store.mutateTaskAndWorkspace({
11130
11306
  taskId: task.data.id,
@@ -11494,7 +11670,7 @@ class WorkitCore {
11494
11670
  activeWorkerBlocker(task, workspace) {
11495
11671
  if (workspace.writer)
11496
11672
  return failure("recovery_required", "writer ownership must be released first");
11497
- if (task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
11673
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11498
11674
  return failure("recovery_required", "worker state requires reconciliation");
11499
11675
  return null;
11500
11676
  }
@@ -11731,11 +11907,6 @@ class WorkitCore {
11731
11907
  if (helper && !helper.ok)
11732
11908
  return helper;
11733
11909
  const evidence = input.evidence;
11734
- if (helper?.ok) {
11735
- const assignment = helper.data.data.assignment;
11736
- 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))
11737
- return failure("permission_denied", "evidence is outside the worker assignment");
11738
- }
11739
11910
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
11740
11911
  return failure("invalid_input", "missing or skipped evidence requires a reason");
11741
11912
  if (evidence.kind === "review") {
@@ -11752,6 +11923,11 @@ class WorkitCore {
11752
11923
  evidence.beforeCandidateId ??= null;
11753
11924
  evidence.candidateId ??= null;
11754
11925
  }
11926
+ if (helper?.ok) {
11927
+ const assignment = helper.data.data.assignment;
11928
+ 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))
11929
+ return failure("permission_denied", "evidence is outside the worker assignment");
11930
+ }
11755
11931
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11756
11932
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
11757
11933
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -12258,7 +12434,12 @@ class WorkitCore {
12258
12434
  return failure("invalid_transition", "worker is not awaiting dispatch");
12259
12435
  return success(mutation.revision, null, {
12260
12436
  ...current,
12261
- workers: current.workers.map((item) => item.id === input.workerId ? { ...item, recordedAt: mutation.now, provenance: attested.data } : item)
12437
+ workers: current.workers.map((item) => item.id === input.workerId ? {
12438
+ ...item,
12439
+ recordedAt: mutation.now,
12440
+ provenance: attested.data,
12441
+ data: { ...item.data, state: "dispatching" }
12442
+ } : item)
12262
12443
  });
12263
12444
  }
12264
12445
  });
@@ -12642,6 +12823,9 @@ class WorkitCore {
12642
12823
  id: task.id,
12643
12824
  revision: task.revision,
12644
12825
  workspaceRevision: workspace.data.revision,
12826
+ createdAt: task.createdAt,
12827
+ updatedAt: task.updatedAt,
12828
+ runtime: task.runtime ?? null,
12645
12829
  objective: task.intent.data.objective,
12646
12830
  status: task.status,
12647
12831
  closure: task.closure,
@@ -12727,7 +12911,7 @@ class WorkitCore {
12727
12911
  const base = reconcileResume(effectiveView);
12728
12912
  if (!base.ok)
12729
12913
  return base;
12730
- const needsReconciliation = view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
12914
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
12731
12915
  const blockers = [...base.data.blockers];
12732
12916
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
12733
12917
  blockers.push({
@@ -12794,7 +12978,8 @@ class WorkitCore {
12794
12978
  ...current,
12795
12979
  status,
12796
12980
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
12797
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
12981
+ progress: current.progress,
12982
+ pauseReason: status === "paused" ? input.reason ?? null : null
12798
12983
  })
12799
12984
  });
12800
12985
  if (!changed.ok)
@@ -12830,7 +13015,7 @@ class WorkitCore {
12830
13015
  return workspace;
12831
13016
  if (!workspace.data)
12832
13017
  return failure("not_found", "workspace not found");
12833
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
13018
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
12834
13019
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
12835
13020
  this.fillRevisions(input, task.data, workspace.data);
12836
13021
  const changed = this.store.mutateTaskAndWorkspace({
@@ -12959,6 +13144,13 @@ var cursorCapabilities = (availability = {}) => {
12959
13144
  reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
12960
13145
  refs: [hostRef("subagentStart")]
12961
13146
  },
13147
+ {
13148
+ name: "fresh-context-review",
13149
+ surface: "subagentStart",
13150
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
13151
+ 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",
13152
+ refs: [hostRef("subagentStart")]
13153
+ },
12962
13154
  {
12963
13155
  name: "arbitrary_shell_write",
12964
13156
  surface: "unobservable_shell",
@@ -12992,7 +13184,7 @@ var parseCursorHookInput = (value) => {
12992
13184
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
12993
13185
  return { ok: false, error: "exactly one workspace root is required" };
12994
13186
  const root = roots[0];
12995
- if (!path6.isAbsolute(root) || !existsSync3(root))
13187
+ if (!path7.isAbsolute(root) || !existsSync4(root))
12996
13188
  return { ok: false, error: "workspace root must be an existing absolute path" };
12997
13189
  const event = value.hook_event_name;
12998
13190
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;