@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.
@@ -7492,7 +7492,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
7492
7492
  const covers = (path) => normOuter.paths.some((base) => base === "." || path === base || path.startsWith(`${base}/`)) && !excluded(path, normOuter);
7493
7493
  const innerExcludes = (path) => innerExclusions.some((excludedPath) => excludedPath === path || path.startsWith(`${excludedPath}/`));
7494
7494
  return innerPaths.every(covers) && !normOuter.exclusions.some((excludedPath) => innerPaths.some((base) => (base === "." || excludedPath === base || excludedPath.startsWith(`${base}/`)) && !innerExcludes(excludedPath)));
7495
- }, refSchema, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
7495
+ }, refSchema, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
7496
7496
  ok: true,
7497
7497
  schemaVersion: 1,
7498
7498
  revision: revisionValue,
@@ -7794,6 +7794,7 @@ var init_task_contract = __esm(() => {
7794
7794
  scope: scopeSchema,
7795
7795
  presented: text,
7796
7796
  approvedContent: text,
7797
+ displayed: text.optional(),
7797
7798
  contentRefs: array(refSchema)
7798
7799
  }).strict(),
7799
7800
  digest,
@@ -7832,7 +7833,7 @@ var init_task_contract = __esm(() => {
7832
7833
  }).strict();
7833
7834
  workerSchema = object({
7834
7835
  assignment: assignmentSchema,
7835
- state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
7836
+ state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
7836
7837
  session: refSchema.nullable(),
7837
7838
  report: workerReportSchema.nullable()
7838
7839
  }).strict();
@@ -7872,6 +7873,10 @@ var init_task_contract = __esm(() => {
7872
7873
  path: ["decisionId"]
7873
7874
  });
7874
7875
  });
7876
+ runtimeSchema = object({
7877
+ createdWith: text.nullable(),
7878
+ updatedWith: text
7879
+ }).strict();
7875
7880
  taskRecordSchema = object({
7876
7881
  schemaVersion: literal(1),
7877
7882
  id,
@@ -7885,6 +7890,8 @@ var init_task_contract = __esm(() => {
7885
7890
  status: _enum(["active", "paused", "closed"]),
7886
7891
  closure: closureSchema.nullable(),
7887
7892
  progress: progressSchema,
7893
+ pauseReason: text.nullable().optional(),
7894
+ runtime: runtimeSchema.optional(),
7888
7895
  assessments: array(entrySchema(assessmentSchema)),
7889
7896
  policy: policySchema.nullable(),
7890
7897
  policyChanges: array(policyChangeSchema),
@@ -7900,6 +7907,7 @@ var init_task_contract = __esm(() => {
7900
7907
  id,
7901
7908
  revision,
7902
7909
  root: nonEmpty,
7910
+ runtime: runtimeSchema.optional(),
7903
7911
  writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
7904
7912
  }).strict();
7905
7913
  capabilitySchema = object({
@@ -7927,6 +7935,9 @@ var init_task_contract = __esm(() => {
7927
7935
  id,
7928
7936
  revision,
7929
7937
  workspaceRevision: revision,
7938
+ createdAt: utc,
7939
+ updatedAt: utc,
7940
+ runtime: runtimeSchema.nullable(),
7930
7941
  objective: text,
7931
7942
  status: _enum(["active", "paused", "closed"]),
7932
7943
  closure: closureSchema.nullable(),
@@ -7996,7 +8007,7 @@ var init_task_contract = __esm(() => {
7996
8007
  expectedWorkspaceRevision: revision.optional(),
7997
8008
  outcome: outcomeSchema,
7998
8009
  summary: text,
7999
- decisionIds: array(id)
8010
+ decisionIds: array(id).optional()
8000
8011
  })
8001
8012
  };
8002
8013
  policyOperations = {
@@ -8088,14 +8099,15 @@ var init_task_contract = __esm(() => {
8088
8099
  ...taskId,
8089
8100
  expectedRevision: revision.optional(),
8090
8101
  expectedWorkspaceRevision: revision.optional(),
8091
- workerId: nullableId.optional()
8102
+ workerId: nullableId.optional(),
8103
+ reason: text.optional()
8092
8104
  }),
8093
8105
  release: operation({
8094
8106
  action: literal("release"),
8095
8107
  ...taskId,
8096
8108
  expectedRevision: revision.optional(),
8097
8109
  expectedWorkspaceRevision: revision.optional(),
8098
- reason: text
8110
+ reason: text.optional()
8099
8111
  })
8100
8112
  };
8101
8113
  stateOperations = {
@@ -8130,6 +8142,21 @@ var init_task_contract = __esm(() => {
8130
8142
  compiledOperationSchemas = Object.fromEntries(OPERATION_FAMILIES.map((family) => [family, compile(operationSchemas[family])]));
8131
8143
  });
8132
8144
 
8145
+ // packages/workit-core/src/core/package-root.ts
8146
+ import { existsSync } from "node:fs";
8147
+ import path from "node:path";
8148
+ import { fileURLToPath } from "node:url";
8149
+ var packageRoot = () => {
8150
+ let dir = path.dirname(fileURLToPath(import.meta.url));
8151
+ while (true) {
8152
+ const parent = path.dirname(dir);
8153
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
8154
+ return dir;
8155
+ dir = parent;
8156
+ }
8157
+ };
8158
+ var init_package_root = () => {};
8159
+
8133
8160
  // node_modules/@openclaw/fs-safe/dist/errors.js
8134
8161
  function categorizeFsSafeError(code) {
8135
8162
  return OPERATIONAL_CODES.has(code) ? "operational" : "policy";
@@ -8240,7 +8267,7 @@ var init_read_open_flags = () => {};
8240
8267
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
8241
8268
  import { randomBytes } from "node:crypto";
8242
8269
  import fsSync from "node:fs";
8243
- import path from "node:path";
8270
+ import path2 from "node:path";
8244
8271
  function parseSidecarLockSnapshot(snapshot, parser) {
8245
8272
  return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
8246
8273
  }
@@ -8268,13 +8295,13 @@ ${ownershipToken}
8268
8295
  return { raw, ownershipToken };
8269
8296
  }
8270
8297
  function relativeSidecarLockPath(lockRoot, lockPath) {
8271
- const resolved = path.resolve(lockPath);
8272
- const lexicalRelative = path.relative(lockRoot.rootDir, resolved);
8273
- const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path.sep}`) && !path.isAbsolute(lexicalRelative) ? lexicalRelative : path.relative(lockRoot.rootReal, resolved);
8274
- if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
8298
+ const resolved = path2.resolve(lockPath);
8299
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
8300
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
8301
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
8275
8302
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
8276
8303
  }
8277
- return relative.split(path.sep).join(path.posix.sep);
8304
+ return relative.split(path2.sep).join(path2.posix.sep);
8278
8305
  }
8279
8306
  function parseSidecarLockPayload(raw, parser) {
8280
8307
  if (parser) {
@@ -8444,7 +8471,7 @@ function sleepSync(ms) {
8444
8471
 
8445
8472
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
8446
8473
  import fs3 from "node:fs";
8447
- import path2 from "node:path";
8474
+ import path3 from "node:path";
8448
8475
  function getSyncHeldLocks() {
8449
8476
  const globalWithState = globalThis;
8450
8477
  if (!globalWithState[SYNC_HELD_LOCKS_KEY]) {
@@ -8522,26 +8549,26 @@ function canonicalLockParentSync(parent) {
8522
8549
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
8523
8550
  }
8524
8551
  function normalizeTargetPath(targetPath) {
8525
- const resolved = path2.resolve(targetPath);
8526
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
8552
+ const resolved = path3.resolve(targetPath);
8553
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
8527
8554
  try {
8528
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
8555
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
8529
8556
  } catch {
8530
8557
  return resolved;
8531
8558
  }
8532
8559
  }
8533
8560
  function boundedLockPath(lockPath, lockRoot) {
8534
- const resolved = path2.resolve(lockPath);
8561
+ const resolved = path3.resolve(lockPath);
8535
8562
  if (!lockRoot)
8536
8563
  return resolved;
8537
8564
  relativeSidecarLockPath(lockRoot, resolved);
8538
- const parent = path2.dirname(resolved);
8565
+ const parent = path3.dirname(resolved);
8539
8566
  const parentReal = canonicalLockParentSync(parent);
8540
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
8541
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
8567
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
8568
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
8542
8569
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
8543
8570
  }
8544
- return path2.join(parentReal, path2.basename(resolved));
8571
+ return path3.join(parentReal, path3.basename(resolved));
8545
8572
  }
8546
8573
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
8547
8574
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -8760,17 +8787,17 @@ var init_file_lock = __esm(() => {
8760
8787
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8761
8788
  import * as fs4 from "node:fs";
8762
8789
  import { hostname } from "node:os";
8763
- import path3 from "node:path";
8790
+ import path4 from "node:path";
8764
8791
 
8765
8792
  class TaskStore {
8766
8793
  root;
8767
8794
  constructor(root) {
8768
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
8795
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
8769
8796
  }
8770
8797
  readTask(taskId) {
8771
8798
  if (!validId(taskId))
8772
8799
  return failure("invalid_input", "task ID is invalid", { taskId });
8773
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8800
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8774
8801
  if (!result.exists)
8775
8802
  return failure("not_found", "task not found", { taskId });
8776
8803
  if (result.result.ok && result.result.data.id !== taskId)
@@ -8804,7 +8831,7 @@ class TaskStore {
8804
8831
  });
8805
8832
  const tasks = [];
8806
8833
  for (const name of names.sort()) {
8807
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
8834
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
8808
8835
  if (!item.exists)
8809
8836
  continue;
8810
8837
  if (!item.result.ok)
@@ -8838,7 +8865,7 @@ class TaskStore {
8838
8865
  if (!current.ok)
8839
8866
  return current;
8840
8867
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
8841
- return failure("revision_conflict", "workspace revision does not match", {
8868
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8842
8869
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
8843
8870
  actualWorkspaceRevision: current.data?.revision ?? null
8844
8871
  });
@@ -8850,11 +8877,17 @@ class TaskStore {
8850
8877
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
8851
8878
  if (current.data && !previousWorkspaceBytes)
8852
8879
  return failure("storage_error", "workspace snapshot disappeared during creation");
8853
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8880
+ const workspace = current.data ? {
8881
+ ...current.data,
8882
+ revision: newRevision(),
8883
+ root: this.root,
8884
+ runtime: stampNew(current.data)
8885
+ } : {
8854
8886
  schemaVersion: SCHEMA_VERSION,
8855
8887
  id: newId(),
8856
8888
  revision: newRevision(),
8857
8889
  root: this.root,
8890
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8858
8891
  writer: null
8859
8892
  };
8860
8893
  const timestamp = value.now ?? now();
@@ -8876,6 +8909,7 @@ class TaskStore {
8876
8909
  status: "active",
8877
8910
  closure: null,
8878
8911
  progress: { summary: "", nextAction: null, blockers: [] },
8912
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8879
8913
  assessments: [],
8880
8914
  policy: null,
8881
8915
  policyChanges: [],
@@ -8906,7 +8940,7 @@ class TaskStore {
8906
8940
  if (!current.ok)
8907
8941
  return current;
8908
8942
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
8909
- return failure("revision_conflict", "workspace revision does not match", {
8943
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8910
8944
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
8911
8945
  actualWorkspaceRevision: current.data?.revision ?? null
8912
8946
  });
@@ -8914,11 +8948,17 @@ class TaskStore {
8914
8948
  if (current.data && !previousWorkspaceBytes)
8915
8949
  return failure("storage_error", "workspace snapshot disappeared during import");
8916
8950
  const timestamp = input.now ?? now();
8917
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8951
+ const workspace = current.data ? {
8952
+ ...current.data,
8953
+ revision: newRevision(),
8954
+ root: this.root,
8955
+ runtime: stampNew(current.data)
8956
+ } : {
8918
8957
  schemaVersion: SCHEMA_VERSION,
8919
8958
  id: input.workspaceId ?? newId(),
8920
8959
  revision: newRevision(),
8921
8960
  root: this.root,
8961
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8922
8962
  writer: null
8923
8963
  };
8924
8964
  const task = {
@@ -8926,7 +8966,11 @@ class TaskStore {
8926
8966
  workspaceId: workspace.id,
8927
8967
  revision: newRevision(),
8928
8968
  createdAt: timestamp,
8929
- updatedAt: timestamp
8969
+ updatedAt: timestamp,
8970
+ runtime: {
8971
+ createdWith: input.task.runtime?.createdWith ?? null,
8972
+ updatedWith: runtimeVersion()
8973
+ }
8930
8974
  };
8931
8975
  const validTask = taskRecordSchema.safeParse(task);
8932
8976
  if (!validTask.success)
@@ -8975,7 +9019,8 @@ class TaskStore {
8975
9019
  workspaceId: current.data.workspaceId,
8976
9020
  createdAt: current.data.createdAt,
8977
9021
  revision: context.revision,
8978
- updatedAt: context.now
9022
+ updatedAt: context.now,
9023
+ runtime: stampNew(current.data)
8979
9024
  };
8980
9025
  const valid = taskRecordSchema.safeParse(record);
8981
9026
  if (!valid.success)
@@ -9009,7 +9054,8 @@ class TaskStore {
9009
9054
  ...changed.data,
9010
9055
  id: current.data.id,
9011
9056
  root: this.root,
9012
- revision: context.revision
9057
+ revision: context.revision,
9058
+ runtime: stampNew(current.data)
9013
9059
  };
9014
9060
  const valid = workspaceRecordSchema.safeParse(record);
9015
9061
  if (!valid.success)
@@ -9052,7 +9098,8 @@ class TaskStore {
9052
9098
  ...changedWorkspace.data,
9053
9099
  id: workspace.data.id,
9054
9100
  root: this.root,
9055
- revision: workspaceContext.revision
9101
+ revision: workspaceContext.revision,
9102
+ runtime: stampNew(workspace.data)
9056
9103
  });
9057
9104
  if (!nextWorkspace.success)
9058
9105
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -9077,7 +9124,8 @@ class TaskStore {
9077
9124
  workspaceId: task.data.workspaceId,
9078
9125
  createdAt: task.data.createdAt,
9079
9126
  revision: taskContext.revision,
9080
- updatedAt: taskContext.now
9127
+ updatedAt: taskContext.now,
9128
+ runtime: stampNew(task.data)
9081
9129
  });
9082
9130
  if (!nextTask.success) {
9083
9131
  this.markUncertain(nextWorkspace.data);
@@ -9104,7 +9152,7 @@ class TaskStore {
9104
9152
  if (match)
9105
9153
  candidates.push({
9106
9154
  target: match[1],
9107
- path: path3.join(this.recoveryDir, name),
9155
+ path: path4.join(this.recoveryDir, name),
9108
9156
  digest: match[3]
9109
9157
  });
9110
9158
  }
@@ -9297,7 +9345,8 @@ class TaskStore {
9297
9345
  const value = {
9298
9346
  ...workspace,
9299
9347
  writer: { ...workspace.writer, state: "uncertain" },
9300
- revision: newRevision()
9348
+ revision: newRevision(),
9349
+ runtime: stampNew(workspace)
9301
9350
  };
9302
9351
  this.replaceSnapshot(this.workspacePath, value, workspace);
9303
9352
  }
@@ -9375,7 +9424,7 @@ class TaskStore {
9375
9424
  }
9376
9425
  fs4.renameSync(temporary, file);
9377
9426
  temporary = undefined;
9378
- this.fsyncDirectory(path3.dirname(file));
9427
+ this.fsyncDirectory(path4.dirname(file));
9379
9428
  return success(null, null, value);
9380
9429
  } catch (error) {
9381
9430
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -9389,9 +9438,9 @@ class TaskStore {
9389
9438
  }
9390
9439
  }
9391
9440
  saveRecovery(file, bytes) {
9392
- const target = path3.basename(file) === "workspace.json" ? "workspace" : "task";
9393
- const id = target === "task" ? path3.basename(file, ".json") : "workspace";
9394
- const destination = path3.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9441
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
9442
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
9443
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9395
9444
  if (fs4.existsSync(destination)) {
9396
9445
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
9397
9446
  return;
@@ -9469,7 +9518,7 @@ class TaskStore {
9469
9518
  for (const name of fs4.readdirSync(this.tasksDir)) {
9470
9519
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
9471
9520
  return null;
9472
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
9521
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
9473
9522
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
9474
9523
  return null;
9475
9524
  ids.add(parsed.data.workspaceId);
@@ -9511,7 +9560,12 @@ class TaskStore {
9511
9560
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
9512
9561
  return failure("unsupported_version", "unsupported snapshot schema version");
9513
9562
  const parsed = schema.safeParse(value);
9514
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
9563
+ if (parsed.success)
9564
+ return success(null, null, parsed.data);
9565
+ const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
9566
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
9567
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
9568
+ return failure("recovery_required", "snapshot does not satisfy its schema");
9515
9569
  }
9516
9570
  processStart(pid) {
9517
9571
  try {
@@ -9521,34 +9575,57 @@ class TaskStore {
9521
9575
  }
9522
9576
  }
9523
9577
  conflict(expected, actual) {
9524
- return failure("revision_conflict", "snapshot revision does not match", {
9578
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
9525
9579
  expectedRevision: expected,
9526
9580
  actualRevision: actual
9527
9581
  });
9528
9582
  }
9529
9583
  taskPath(taskId) {
9530
- return path3.join(this.tasksDir, `${taskId}.json`);
9584
+ return path4.join(this.tasksDir, `${taskId}.json`);
9531
9585
  }
9532
9586
  get workitDir() {
9533
- return path3.join(this.root, ".workit");
9587
+ return path4.join(this.root, ".workit");
9534
9588
  }
9535
9589
  get tasksDir() {
9536
- return path3.join(this.workitDir, "tasks");
9590
+ return path4.join(this.workitDir, "tasks");
9537
9591
  }
9538
9592
  get recoveryDir() {
9539
- return path3.join(this.workitDir, "recovery");
9593
+ return path4.join(this.workitDir, "recovery");
9540
9594
  }
9541
9595
  get workspacePath() {
9542
- return path3.join(this.workitDir, "workspace.json");
9596
+ return path4.join(this.workitDir, "workspace.json");
9543
9597
  }
9544
9598
  get lockPath() {
9545
- return path3.join(this.workitDir, "metadata.lock");
9599
+ return path4.join(this.workitDir, "metadata.lock");
9546
9600
  }
9547
9601
  get gitignorePath() {
9548
- return path3.join(this.workitDir, ".gitignore");
9602
+ return path4.join(this.workitDir, ".gitignore");
9549
9603
  }
9550
9604
  }
9551
- var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), jsonBytes = (value) => `${canonicalJson(value)}
9605
+ var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), cachedRuntimeVersion = null, runtimeVersion = () => {
9606
+ if (cachedRuntimeVersion === null) {
9607
+ try {
9608
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
9609
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
9610
+ } catch {
9611
+ cachedRuntimeVersion = "0.0.0";
9612
+ }
9613
+ }
9614
+ return cachedRuntimeVersion;
9615
+ }, stampNew = (record) => ({
9616
+ createdWith: record.runtime?.createdWith ?? null,
9617
+ updatedWith: runtimeVersion()
9618
+ }), isNewerVersion = (candidate, current) => {
9619
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
9620
+ const [next, now] = [parts(candidate), parts(current)];
9621
+ for (let index = 0;index < 3; index += 1) {
9622
+ const left = next[index] ?? 0;
9623
+ const right = now[index] ?? 0;
9624
+ if (left !== right)
9625
+ return left > right;
9626
+ }
9627
+ return false;
9628
+ }, jsonBytes = (value) => `${canonicalJson(value)}
9552
9629
  `, digestBytes = (value) => createHash2("sha256").update(value).digest("hex"), isObject2 = (value) => typeof value === "object" && value !== null, validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value), validDigest = (value) => /^[0-9a-f]{64}$/.test(value), parseMetadataLock = (raw) => {
9553
9630
  let value;
9554
9631
  try {
@@ -9566,6 +9643,7 @@ var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOStrin
9566
9643
  };
9567
9644
  var init_task_store = __esm(() => {
9568
9645
  init_zod();
9646
+ init_package_root();
9569
9647
  init_file_lock();
9570
9648
  init_task_contract();
9571
9649
  processEvidenceSchema = object({
@@ -9582,10 +9660,63 @@ var init_task_store = __esm(() => {
9582
9660
  }).strict();
9583
9661
  });
9584
9662
 
9663
+ // packages/workit-core/src/core/workers.ts
9664
+ function assertProductWriteAllowed(input) {
9665
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9666
+ return failure("invalid_input", "invalid product write path");
9667
+ if (!input.store)
9668
+ return failure("permission_denied", "write authorization requires core state");
9669
+ const task = input.store.readTask(input.task.id);
9670
+ if (!task.ok)
9671
+ return task;
9672
+ const workspace = input.store.readWorkspace();
9673
+ if (!workspace.ok)
9674
+ return workspace;
9675
+ if (!workspace.data)
9676
+ return failure("not_found", "workspace not found");
9677
+ const workspaceRecord = workspace.data;
9678
+ if (task.data.status !== "active")
9679
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
9680
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
9681
+ return failure("recovery_required", "task and workspace bindings are invalid");
9682
+ const writer = workspaceRecord.writer;
9683
+ if (!writer)
9684
+ return failure("permission_denied", "checkout has no writer owner");
9685
+ if (writer.state === "uncertain")
9686
+ return failure("recovery_required", "checkout writer requires recovery", {
9687
+ owner: writer.owner
9688
+ });
9689
+ const caller = callerValue(input.caller);
9690
+ const workerId = workerIdOf(input.caller);
9691
+ const owner = writer.owner;
9692
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
9693
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
9694
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
9695
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
9696
+ return failure("permission_denied", "only implementers can own product writes");
9697
+ 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))))
9698
+ return failure("recovery_required", "worker session is not an active writer");
9699
+ return success(null, workspaceRecord.revision, owner);
9700
+ }
9701
+ var same = (left, right) => {
9702
+ try {
9703
+ return canonicalJson(left) === canonicalJson(right);
9704
+ } catch {
9705
+ return false;
9706
+ }
9707
+ }, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9708
+ const code = char.charCodeAt(0);
9709
+ return code < 32 || code === 127;
9710
+ }) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
9711
+ var init_workers = __esm(() => {
9712
+ init_task_contract();
9713
+ UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9714
+ });
9715
+
9585
9716
  // packages/workit-core/src/core/authority.ts
9586
9717
  import { createHash as createHash3 } from "node:crypto";
9587
9718
  import * as fs5 from "node:fs";
9588
- import path4 from "node:path";
9719
+ import path5 from "node:path";
9589
9720
  function applicableDecision(task, purpose, binding, checkoutRoot) {
9590
9721
  if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
9591
9722
  return [];
@@ -9969,20 +10100,31 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
9969
10100
  continue;
9970
10101
  if (!reference.digest)
9971
10102
  return failure("invalid_input", "document references require a byte digest");
9972
- const target = path4.resolve(root, reference.path);
9973
- if (target !== root && !target.startsWith(`${root}${path4.sep}`))
9974
- return failure("invalid_input", "document reference escapes checkout");
10103
+ const target = path5.resolve(root, reference.path);
10104
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
10105
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
10106
+ fields: [
10107
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
10108
+ ]
10109
+ });
9975
10110
  try {
9976
- const relative = path4.relative(root, target);
10111
+ const relative = path5.relative(root, target);
9977
10112
  let current = root;
9978
- for (const segment of relative.split(path4.sep)) {
10113
+ for (const segment of relative.split(path5.sep)) {
9979
10114
  if (!segment)
9980
10115
  continue;
9981
- current = path4.join(current, segment);
10116
+ current = path5.join(current, segment);
9982
10117
  const stat = fs5.lstatSync(current);
9983
10118
  const real = fs5.realpathSync(current);
9984
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
9985
- return failure("invalid_input", "document reference escapes checkout");
10119
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
10120
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
10121
+ fields: [
10122
+ {
10123
+ path: "contentRefs.path",
10124
+ reason: `${reference.path} resolves outside the checkout`
10125
+ }
10126
+ ]
10127
+ });
9986
10128
  if (stat.isSymbolicLink())
9987
10129
  return failure("invalid_input", "document reference uses a symlink");
9988
10130
  if (current === target && !stat.isFile())
@@ -10059,7 +10201,7 @@ var init_authority = __esm(() => {
10059
10201
  import { createHash as createHash4 } from "node:crypto";
10060
10202
  import * as fs6 from "node:fs";
10061
10203
  import { spawnSync } from "node:child_process";
10062
- import path5 from "node:path";
10204
+ import path6 from "node:path";
10063
10205
  function captureCandidate(root, scope, environment = []) {
10064
10206
  let checkout;
10065
10207
  try {
@@ -10069,7 +10211,7 @@ function captureCandidate(root, scope, environment = []) {
10069
10211
  }
10070
10212
  const normalizedScope = canonicalScope(scope);
10071
10213
  if (!normalizedScope)
10072
- return failure("invalid_input", "candidate scope is invalid");
10214
+ 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" }] });
10073
10215
  const roots = scopeRoots(checkout, normalizedScope);
10074
10216
  if (!roots.ok)
10075
10217
  return roots;
@@ -10101,7 +10243,7 @@ function captureCandidate(root, scope, environment = []) {
10101
10243
  for (const item of [...names].sort()) {
10102
10244
  if (!scopeMatches(item, normalizedScope))
10103
10245
  continue;
10104
- const target = path5.join(checkout, item);
10246
+ const target = path6.join(checkout, item);
10105
10247
  try {
10106
10248
  const stat = fs6.lstatSync(target);
10107
10249
  if (stat.isSymbolicLink()) {
@@ -10220,7 +10362,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10220
10362
  return true;
10221
10363
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10222
10364
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10223
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
10365
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10224
10366
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10225
10367
  });
10226
10368
  if (passed.length) {
@@ -10316,7 +10458,17 @@ function evaluateClosure(requestedOutcome, view) {
10316
10458
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10317
10459
  if (requestedOutcome !== "stopped" && blocking.length)
10318
10460
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10319
- requirementIds: blocking.map((item) => item.requirementId)
10461
+ requirementIds: blocking.map((item) => item.requirementId),
10462
+ requirements: blocking.map((item) => {
10463
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10464
+ return {
10465
+ requirementId: item.requirementId,
10466
+ ruleId: requirement?.ruleId ?? "unknown",
10467
+ reason: item.reason,
10468
+ satisfaction: requirement?.satisfaction ?? "",
10469
+ dependentAction: requirement?.dependentAction ?? null
10470
+ };
10471
+ })
10320
10472
  });
10321
10473
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
10322
10474
  if (requestedOutcome === "verified" && accepted.length)
@@ -10342,12 +10494,12 @@ function evaluateClosure(requestedOutcome, view) {
10342
10494
  requirementIds: evaluations.map((item) => item.requirementId)
10343
10495
  });
10344
10496
  }
10345
- var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path5.sep}`), relative = (root, candidate) => path5.relative(root, candidate).split(path5.sep).join("/") || ".", canonicalPath = (value) => {
10497
+ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`), relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".", canonicalPath = (value) => {
10346
10498
  if (typeof value !== "string" || !value)
10347
10499
  return null;
10348
10500
  if (value.includes("\\") || value.split("/").includes(".."))
10349
10501
  return null;
10350
- const normalized = path5.posix.normalize(value);
10502
+ const normalized = path6.posix.normalize(value);
10351
10503
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
10352
10504
  return null;
10353
10505
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10389,14 +10541,14 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10389
10541
  git: /not a git repository/i.test(stderr) ? false : true
10390
10542
  };
10391
10543
  }
10392
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
10544
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
10393
10545
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10394
10546
  if (stagedDeleted.status !== 0) {
10395
10547
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10396
10548
  if (!/not a git repository/i.test(stderr))
10397
10549
  return { paths, uncertain: true, git: true };
10398
10550
  } else if (stagedDeleted.stdout) {
10399
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
10551
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
10400
10552
  }
10401
10553
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10402
10554
  }, walk = (root, directory, output) => {
@@ -10410,7 +10562,7 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10410
10562
  for (const name of names) {
10411
10563
  if (name === ".git" || name === ".workit")
10412
10564
  continue;
10413
- const target = path5.join(directory, name);
10565
+ const target = path6.join(directory, name);
10414
10566
  let stat;
10415
10567
  try {
10416
10568
  stat = fs6.lstatSync(target);
@@ -10427,17 +10579,17 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
10427
10579
  }, scopeRoots = (root, scope) => {
10428
10580
  const roots = [];
10429
10581
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10430
- const target = path5.resolve(root, item);
10582
+ const target = path6.resolve(root, item);
10431
10583
  if (!inside(root, target))
10432
- return failure("invalid_input", "candidate scope escapes checkout");
10584
+ 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` }] });
10433
10585
  let ancestor = target;
10434
10586
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10435
- ancestor = path5.dirname(ancestor);
10587
+ ancestor = path6.dirname(ancestor);
10436
10588
  try {
10437
10589
  if (!inside(root, fs6.realpathSync(ancestor)))
10438
- return failure("invalid_input", "candidate scope escapes checkout");
10590
+ 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` }] });
10439
10591
  } catch {
10440
- return failure("invalid_input", "candidate scope cannot be inspected");
10592
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
10441
10593
  }
10442
10594
  roots.push(target);
10443
10595
  }
@@ -10478,6 +10630,106 @@ var init_task_evaluation = __esm(() => {
10478
10630
  scopeCovers2 = scopeCovers;
10479
10631
  });
10480
10632
 
10633
+ // packages/workit-core/src/core/methods.ts
10634
+ function selectMethods(policy, capabilities) {
10635
+ return Object.entries(METHODS).flatMap(([id, definition]) => {
10636
+ const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
10637
+ if (requirements.length === 0)
10638
+ return [];
10639
+ const capability = assuranceFor(id, definition, capabilities);
10640
+ const reasons = [
10641
+ ...new Set(requirements.map((requirement) => requirement.reason)),
10642
+ ...capability.reasons
10643
+ ];
10644
+ if (capability.assurance === "unavailable")
10645
+ reasons.unshift("required capability is unavailable");
10646
+ return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
10647
+ });
10648
+ }
10649
+ var METHODS, methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
10650
+ const keys = new Set([
10651
+ id,
10652
+ ...definition.dimensions ?? [],
10653
+ ...definition.ruleIds ?? []
10654
+ ]);
10655
+ const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
10656
+ if (id === "workit-review" && matches.length === 0)
10657
+ return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
10658
+ if (matches.some((capability) => capability.assurance === "unavailable"))
10659
+ return {
10660
+ assurance: "unavailable",
10661
+ reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
10662
+ };
10663
+ if (matches.some((capability) => capability.assurance === "enforced"))
10664
+ return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
10665
+ return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
10666
+ }, invariantBootstrap = () => `
10667
+ Workit keeps one accountable lead and one shared task state. Inspect current task
10668
+ state before acting. An empty task.list means no session yet, not permission to
10669
+ skip Workit; for user-requested product, debug, or behavior work with no active
10670
+ or paused task, run task.start then policy.assess before other product mutations.
10671
+ Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
10672
+ to the current records; explicit values are still concurrency-checked, so never
10673
+ copy revisions between calls. Happy path: task.list, task.start {intent} with no
10674
+ revisions, policy.assess {assessment: {facts, signals, consequences,
10675
+ verification}} where facts are inferred or observed with file refs,
10676
+ writer.acquire {taskId} before product writes, evidence.record {evidence} where
10677
+ check and review kinds auto-bind the current tree and need no digests. Record
10678
+ self-review requirements with kind review and reviewContext matching your own
10679
+ host session; the lead may also record checks. Fresh-context review requires a
10680
+ session distinct from the task creator and every other evidence recorder. Record
10681
+ verification evidence last: later evidence or commits can reopen fixed findings
10682
+ for re-verification, so verify, resolve, then close with nothing in between.
10683
+ decision.record {binding with taskId/workspaceId from inspect plus presented and
10684
+ approvedContent} only through a native approval question asked receipt-shaped:
10685
+ header Workit decision: <purpose>, the same label repeated in the question
10686
+ text (some host UIs do not render headers), exactly two options approved and
10687
+ rejected; ask once, never re-ask to mint a receipt. Before asking, present the
10688
+ item in the conversation (durable artifacts: complete digest plus exact path;
10689
+ inline plans: the content) and keep the question to one short scoped sentence;
10690
+ never ask to approve something unseen. An answer with custom text is steering,
10691
+ not approval: adjust, re-present, then ask a new scoped question. After the user
10692
+ approves a spec or plan, execute the remaining tasks continuously with one
10693
+ atomic commit per task and do not ask whether to continue; stop only for a new
10694
+ product decision, a failed safety or verification gate, a conflicting edit, or
10695
+ missing authority. Record the plan's commit list once through the plan-scoped
10696
+ action approval (git.commit with plan_steps and plan_branch) so each listed
10697
+ commit executes without a new question; an unlisted message needs a fresh
10698
+ approval. task.close {outcome,
10699
+ summary, decisionIds}. A completed native subagent run stops its bound worker
10700
+ by itself; if a worker strands in cancelling with its run verifiably over,
10701
+ repeat worker.cancel {reason} to confirm the stop. Check and review evidence
10702
+ without a bound candidate goes stale; findings close only as fixed with passing
10703
+ verification, dismissed with supporting evidence, or deferred under an approved
10704
+ limitation. Use only the
10705
+ shared operations for task, policy, evidence, finding, decision, worker, writer,
10706
+ and state changes. Authority is bounded by the requested scope, current
10707
+ revision, caller/session provenance, and observed capabilities. Never claim host
10708
+ enforcement or evidence that the host cannot provide. Shell-executed writes are
10709
+ unattested agent-guided work even when a writer is held. Preserve unresolved
10710
+ requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
10711
+ always load on demand. Model-invoked: new instructions, interruptions, or
10712
+ forgotten items mid-task → load workit-steer before responding; about to open
10713
+ a PR or fresh from implementing → load workit-deslop; CI red → load
10714
+ workit-green-run; small-looking change before close or merge → load
10715
+ workit-blast-radius; ambiguous, consequential, or disputed proposal before
10716
+ recommending → load workit-challenge; spec or plan needs a flow diagram →
10717
+ load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
10718
+ act from memory of it.
10719
+ `.trim();
10720
+ var init_methods = __esm(() => {
10721
+ METHODS = {
10722
+ "workit-challenge": { dimensions: ["challenge", "decisions"] },
10723
+ "workit-behavioral-tdd": { dimensions: ["testing"] },
10724
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
10725
+ "workit-plan": { dimensions: ["artifacts", "continuity"] },
10726
+ "workit-implement": { dimensions: ["delegation"] },
10727
+ "workit-debug": { ruleIds: ["root-cause-investigation"] },
10728
+ "workit-handoff": { ruleIds: ["durable-handoff"] },
10729
+ "workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
10730
+ };
10731
+ });
10732
+
10481
10733
  // packages/workit-core/src/core/task-context.ts
10482
10734
  function reconcileResume(view, observations = []) {
10483
10735
  if (observations.length > 0)
@@ -10487,7 +10739,7 @@ function reconcileResume(view, observations = []) {
10487
10739
  return candidate;
10488
10740
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
10489
10741
  const blockers = [...view.task.progress.blockers];
10490
- if (view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
10742
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
10491
10743
  blockers.push({
10492
10744
  reason: "worker state requires reconciliation",
10493
10745
  dependentAction: "resume",
@@ -10509,6 +10761,11 @@ function compactTaskContext(view) {
10509
10761
  digest: entry.data.digest,
10510
10762
  references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
10511
10763
  }));
10764
+ const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
10765
+ id: method.id,
10766
+ assurance: method.assurance,
10767
+ reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
10768
+ })) : [];
10512
10769
  const gaps = Array.from(new Set([
10513
10770
  ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
10514
10771
  ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
@@ -10518,15 +10775,18 @@ function compactTaskContext(view) {
10518
10775
  objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
10519
10776
  status: view.task.status,
10520
10777
  decisions,
10778
+ methods,
10521
10779
  gaps,
10522
10780
  nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
10523
10781
  };
10524
10782
  let encoded = JSON.stringify(context);
10525
- while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.gaps.length)) {
10783
+ while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
10526
10784
  if (context.gaps.length)
10527
10785
  context.gaps.pop();
10528
- else
10786
+ else if (context.decisions.length)
10529
10787
  context.decisions.pop();
10788
+ else
10789
+ context.methods.pop();
10530
10790
  encoded = JSON.stringify(context);
10531
10791
  }
10532
10792
  if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
@@ -10562,58 +10822,17 @@ var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, s
10562
10822
  task: bundle.task
10563
10823
  });
10564
10824
  var init_task_context = __esm(() => {
10825
+ init_workers();
10565
10826
  init_task_contract();
10566
10827
  init_task_evaluation();
10828
+ init_methods();
10567
10829
  sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
10568
10830
  sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
10569
10831
  privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
10570
10832
  });
10571
10833
 
10572
- // packages/workit-core/src/core/methods.ts
10573
- var invariantBootstrap = () => `
10574
- Workit keeps one accountable lead and one shared task state. Inspect current task
10575
- state before acting. An empty task.list means no session yet, not permission to
10576
- skip Workit; for user-requested product, debug, or behavior work with no active
10577
- or paused task, run task.start then policy.assess before other product mutations.
10578
- Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
10579
- to the current records; explicit values are still concurrency-checked, so never
10580
- copy revisions between calls. Happy path: task.list, task.start {intent} with no
10581
- revisions, policy.assess {assessment: {facts, signals, consequences,
10582
- verification}} where facts are inferred or observed with file refs,
10583
- writer.acquire {taskId} before product writes, evidence.record {evidence} where
10584
- check and review kinds auto-bind the current tree and need no digests. Record
10585
- self-review requirements with kind review and reviewContext matching your own
10586
- host session; the lead may also record checks. Fresh-context review requires a
10587
- session distinct from the task creator and every other evidence recorder. Record
10588
- verification evidence last: later evidence or commits can reopen fixed findings
10589
- for re-verification, so verify, resolve, then close with nothing in between.
10590
- decision.record {binding with taskId/workspaceId from inspect plus presented and
10591
- approvedContent} only through a native approval question asked receipt-shaped:
10592
- header Workit decision: <purpose>, the same label repeated in the question
10593
- text (some host UIs do not render headers), exactly two options approved and
10594
- rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
10595
- summary, decisionIds}. A completed native subagent run stops its bound worker
10596
- by itself; if a worker strands in cancelling with its run verifiably over,
10597
- repeat worker.cancel {reason} to confirm the stop. Check and review evidence
10598
- without a bound candidate goes stale; findings close only as fixed with passing
10599
- verification, dismissed with supporting evidence, or deferred under an approved
10600
- limitation. Use only the
10601
- shared operations for task, policy, evidence, finding, decision, worker, writer,
10602
- and state changes. Authority is bounded by the requested scope, current
10603
- revision, caller/session provenance, and observed capabilities. Never claim host
10604
- enforcement or evidence that the host cannot provide. Shell-executed writes are
10605
- unattested agent-guided work even when a writer is held. Preserve unresolved
10606
- requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
10607
- always load on demand. Model-invoked: new instructions, interruptions, or
10608
- forgotten items mid-task → load workit-steer before responding; about to open
10609
- a PR or fresh from implementing → load workit-deslop; CI red → load
10610
- workit-green-run; small-looking change before close or merge → load
10611
- workit-blast-radius; ambiguous, consequential, or disputed proposal before
10612
- recommending → load workit-challenge; spec or plan needs a flow diagram →
10613
- load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
10614
- act from memory of it.
10615
- `.trim();
10616
- var init_methods = () => {};
10834
+ // packages/workit-core/src/core/route-intent.ts
10835
+ var init_route_intent = () => {};
10617
10836
 
10618
10837
  // packages/workit-core/src/core/external-action.ts
10619
10838
  var externalActionSchema;
@@ -10632,7 +10851,11 @@ var init_external_action = __esm(() => {
10632
10851
  }).strict(),
10633
10852
  object({
10634
10853
  operation: literal("git.commit"),
10635
- payload: object({ message: string2().min(1) }).strict()
10854
+ payload: object({
10855
+ message: string2().min(1).optional(),
10856
+ plan_steps: array(string2().min(1)).optional(),
10857
+ plan_branch: string2().min(1).optional()
10858
+ }).strict()
10636
10859
  }).strict(),
10637
10860
  object({
10638
10861
  operation: literal("git.push"),
@@ -10764,7 +10987,7 @@ function resolveRequirements(input) {
10764
10987
  ].join(", ")}`,
10765
10988
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
10766
10989
  before: "dependent_action",
10767
- dependentAction: "dependent-action",
10990
+ dependentAction: null,
10768
10991
  acceptanceAllowed: false
10769
10992
  }));
10770
10993
  if (signals.productChoiceOpen.value === true)
@@ -10775,7 +10998,7 @@ function resolveRequirements(input) {
10775
10998
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
10776
10999
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
10777
11000
  before: "dependent_action",
10778
- dependentAction: "dependent-action",
11001
+ dependentAction: null,
10779
11002
  acceptanceAllowed: false
10780
11003
  }));
10781
11004
  if (signals.behaviorChange.value === true)
@@ -11027,58 +11250,6 @@ var init_policy_resolver = __esm(() => {
11027
11250
  }).strict();
11028
11251
  });
11029
11252
 
11030
- // packages/workit-core/src/core/workers.ts
11031
- function assertProductWriteAllowed(input) {
11032
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
11033
- return failure("invalid_input", "invalid product write path");
11034
- if (!input.store)
11035
- return failure("permission_denied", "write authorization requires core state");
11036
- const task = input.store.readTask(input.task.id);
11037
- if (!task.ok)
11038
- return task;
11039
- const workspace = input.store.readWorkspace();
11040
- if (!workspace.ok)
11041
- return workspace;
11042
- if (!workspace.data)
11043
- return failure("not_found", "workspace not found");
11044
- const workspaceRecord = workspace.data;
11045
- if (task.data.status !== "active")
11046
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
11047
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
11048
- return failure("recovery_required", "task and workspace bindings are invalid");
11049
- const writer = workspaceRecord.writer;
11050
- if (!writer)
11051
- return failure("permission_denied", "checkout has no writer owner");
11052
- if (writer.state === "uncertain")
11053
- return failure("recovery_required", "checkout writer requires recovery", {
11054
- owner: writer.owner
11055
- });
11056
- const caller = callerValue(input.caller);
11057
- const workerId = workerIdOf(input.caller);
11058
- const owner = writer.owner;
11059
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
11060
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
11061
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
11062
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
11063
- return failure("permission_denied", "only implementers can own product writes");
11064
- 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))))
11065
- return failure("recovery_required", "worker session is not an active writer");
11066
- return success(null, workspaceRecord.revision, owner);
11067
- }
11068
- var same = (left, right) => {
11069
- try {
11070
- return canonicalJson(left) === canonicalJson(right);
11071
- } catch {
11072
- return false;
11073
- }
11074
- }, callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
11075
- const code = char.charCodeAt(0);
11076
- return code < 32 || code === 127;
11077
- }) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
11078
- var init_workers = __esm(() => {
11079
- init_task_contract();
11080
- });
11081
-
11082
11253
  // packages/workit-core/src/core/task-engine.ts
11083
11254
  import { realpathSync as realpathSync4 } from "node:fs";
11084
11255
 
@@ -11140,7 +11311,7 @@ class WorkitCore {
11140
11311
  activeWorkerBlocker(task, workspace) {
11141
11312
  if (workspace.writer)
11142
11313
  return failure("recovery_required", "writer ownership must be released first");
11143
- if (task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
11314
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11144
11315
  return failure("recovery_required", "worker state requires reconciliation");
11145
11316
  return null;
11146
11317
  }
@@ -11377,11 +11548,6 @@ class WorkitCore {
11377
11548
  if (helper && !helper.ok)
11378
11549
  return helper;
11379
11550
  const evidence = input.evidence;
11380
- if (helper?.ok) {
11381
- const assignment = helper.data.data.assignment;
11382
- 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))
11383
- return failure("permission_denied", "evidence is outside the worker assignment");
11384
- }
11385
11551
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
11386
11552
  return failure("invalid_input", "missing or skipped evidence requires a reason");
11387
11553
  if (evidence.kind === "review") {
@@ -11398,6 +11564,11 @@ class WorkitCore {
11398
11564
  evidence.beforeCandidateId ??= null;
11399
11565
  evidence.candidateId ??= null;
11400
11566
  }
11567
+ if (helper?.ok) {
11568
+ const assignment = helper.data.data.assignment;
11569
+ 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))
11570
+ return failure("permission_denied", "evidence is outside the worker assignment");
11571
+ }
11401
11572
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11402
11573
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
11403
11574
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -11904,7 +12075,12 @@ class WorkitCore {
11904
12075
  return failure("invalid_transition", "worker is not awaiting dispatch");
11905
12076
  return success(mutation.revision, null, {
11906
12077
  ...current,
11907
- workers: current.workers.map((item) => item.id === input.workerId ? { ...item, recordedAt: mutation.now, provenance: attested.data } : item)
12078
+ workers: current.workers.map((item) => item.id === input.workerId ? {
12079
+ ...item,
12080
+ recordedAt: mutation.now,
12081
+ provenance: attested.data,
12082
+ data: { ...item.data, state: "dispatching" }
12083
+ } : item)
11908
12084
  });
11909
12085
  }
11910
12086
  });
@@ -12288,6 +12464,9 @@ class WorkitCore {
12288
12464
  id: task.id,
12289
12465
  revision: task.revision,
12290
12466
  workspaceRevision: workspace.data.revision,
12467
+ createdAt: task.createdAt,
12468
+ updatedAt: task.updatedAt,
12469
+ runtime: task.runtime ?? null,
12291
12470
  objective: task.intent.data.objective,
12292
12471
  status: task.status,
12293
12472
  closure: task.closure,
@@ -12373,7 +12552,7 @@ class WorkitCore {
12373
12552
  const base = reconcileResume(effectiveView);
12374
12553
  if (!base.ok)
12375
12554
  return base;
12376
- const needsReconciliation = view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
12555
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
12377
12556
  const blockers = [...base.data.blockers];
12378
12557
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
12379
12558
  blockers.push({
@@ -12440,7 +12619,8 @@ class WorkitCore {
12440
12619
  ...current,
12441
12620
  status,
12442
12621
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
12443
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
12622
+ progress: current.progress,
12623
+ pauseReason: status === "paused" ? input.reason ?? null : null
12444
12624
  })
12445
12625
  });
12446
12626
  if (!changed.ok)
@@ -12476,7 +12656,7 @@ class WorkitCore {
12476
12656
  return workspace;
12477
12657
  if (!workspace.data)
12478
12658
  return failure("not_found", "workspace not found");
12479
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
12659
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
12480
12660
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
12481
12661
  this.fillRevisions(input, task.data, workspace.data);
12482
12662
  const changed = this.store.mutateTaskAndWorkspace({
@@ -12664,10 +12844,10 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
12664
12844
  const notStarted = input.dispatch === true;
12665
12845
  if (notStarted && (input.state !== "stopped" || input.session !== null))
12666
12846
  return failure("invalid_input", "a never-dispatched worker stops with no session");
12667
- if (notStarted && (entry.data.state === "stopped" || entry.data.state === "unknown"))
12668
- return failure("invalid_transition", "worker is no longer awaiting dispatch");
12669
- if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
12847
+ if (notStarted && entry.data.session !== null)
12670
12848
  return failure("invalid_transition", "worker already has an observed session");
12849
+ if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
12850
+ return failure("invalid_transition", "worker launch was not claimed");
12671
12851
  if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
12672
12852
  return failure("invalid_input", "running worker observations require a session");
12673
12853
  if (entry.data.session && !sameValue(entry.data.session, input.session))
@@ -12676,15 +12856,20 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
12676
12856
  return failure("invalid_transition", "worker has not started");
12677
12857
  if (input.state === "running" && entry.data.state === "stopped")
12678
12858
  return failure("invalid_transition", "stopped worker cannot run again");
12859
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
12679
12860
  const nextEntry = {
12680
12861
  ...entry,
12681
12862
  recordedAt: input.now ?? entry.recordedAt,
12682
12863
  provenance: authority.provenance,
12683
- data: { ...entry.data, state: input.state, session: input.session }
12864
+ data: {
12865
+ ...entry.data,
12866
+ state: terminalCancel ? "cancelling" : input.state,
12867
+ session: input.session
12868
+ }
12684
12869
  };
12685
12870
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
12686
12871
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
12687
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
12872
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
12688
12873
  return success(task.data.revision, workspace.data.revision, entry);
12689
12874
  const changed = input.store.mutateTaskAndWorkspace({
12690
12875
  taskId: task.data.id,
@@ -13002,6 +13187,7 @@ var init_core3 = __esm(() => {
13002
13187
  init_task_store();
13003
13188
  init_task_context();
13004
13189
  init_methods();
13190
+ init_route_intent();
13005
13191
  init_authority();
13006
13192
  init_external_action();
13007
13193
  init_external_action();
@@ -13011,8 +13197,8 @@ var init_core3 = __esm(() => {
13011
13197
  });
13012
13198
 
13013
13199
  // packages/workit-cursor/hooks/workit-hook.ts
13014
- import { existsSync as existsSync3, realpathSync as realpathSync5 } from "node:fs";
13015
- import path6 from "node:path";
13200
+ import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
13201
+ import path7 from "node:path";
13016
13202
  var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapabilities = (availability = {}) => {
13017
13203
  const has = (name) => availability[name] === true;
13018
13204
  return [
@@ -13044,6 +13230,13 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
13044
13230
  reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
13045
13231
  refs: [hostRef("subagentStart")]
13046
13232
  },
13233
+ {
13234
+ name: "fresh-context-review",
13235
+ surface: "subagentStart",
13236
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
13237
+ 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",
13238
+ refs: [hostRef("subagentStart")]
13239
+ },
13047
13240
  {
13048
13241
  name: "arbitrary_shell_write",
13049
13242
  surface: "unobservable_shell",
@@ -13066,7 +13259,7 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
13066
13259
  if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
13067
13260
  return { ok: false, error: "exactly one workspace root is required" };
13068
13261
  const root = roots[0];
13069
- if (!path6.isAbsolute(root) || !existsSync3(root))
13262
+ if (!path7.isAbsolute(root) || !existsSync4(root))
13070
13263
  return { ok: false, error: "workspace root must be an existing absolute path" };
13071
13264
  const event = value.hook_event_name;
13072
13265
  const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;