@brainervirus/workit-cursor 1.0.9 → 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.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +277 -145
- package/dist/mcp-server.js +354 -216
- package/dist/workit-hook.js +279 -145
- package/package.json +3 -3
- package/skills/workit-plan/SKILL.md +14 -2
|
@@ -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,
|
|
@@ -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
|
|
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 =
|
|
8272
|
-
const lexicalRelative =
|
|
8273
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8274
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
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(
|
|
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
|
|
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 =
|
|
8526
|
-
fs3.mkdirSync(
|
|
8552
|
+
const resolved = path3.resolve(targetPath);
|
|
8553
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8527
8554
|
try {
|
|
8528
|
-
return
|
|
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 =
|
|
8561
|
+
const resolved = path3.resolve(lockPath);
|
|
8535
8562
|
if (!lockRoot)
|
|
8536
8563
|
return resolved;
|
|
8537
8564
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8538
|
-
const parent =
|
|
8565
|
+
const parent = path3.dirname(resolved);
|
|
8539
8566
|
const parentReal = canonicalLockParentSync(parent);
|
|
8540
|
-
const parentRelative =
|
|
8541
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
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
|
|
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
|
|
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) :
|
|
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(
|
|
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(
|
|
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 ? {
|
|
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 ? {
|
|
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:
|
|
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(
|
|
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 =
|
|
9393
|
-
const id = target === "task" ?
|
|
9394
|
-
const destination =
|
|
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(
|
|
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
|
-
|
|
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
|
|
9584
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9531
9585
|
}
|
|
9532
9586
|
get workitDir() {
|
|
9533
|
-
return
|
|
9587
|
+
return path4.join(this.root, ".workit");
|
|
9534
9588
|
}
|
|
9535
9589
|
get tasksDir() {
|
|
9536
|
-
return
|
|
9590
|
+
return path4.join(this.workitDir, "tasks");
|
|
9537
9591
|
}
|
|
9538
9592
|
get recoveryDir() {
|
|
9539
|
-
return
|
|
9593
|
+
return path4.join(this.workitDir, "recovery");
|
|
9540
9594
|
}
|
|
9541
9595
|
get workspacePath() {
|
|
9542
|
-
return
|
|
9596
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9543
9597
|
}
|
|
9544
9598
|
get lockPath() {
|
|
9545
|
-
return
|
|
9599
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9546
9600
|
}
|
|
9547
9601
|
get gitignorePath() {
|
|
9548
|
-
return
|
|
9602
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
9549
9603
|
}
|
|
9550
9604
|
}
|
|
9551
|
-
var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
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
|
|
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 =
|
|
9973
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
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 =
|
|
10111
|
+
const relative = path5.relative(root, target);
|
|
9977
10112
|
let current = root;
|
|
9978
|
-
for (const segment of relative.split(
|
|
10113
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9979
10114
|
if (!segment)
|
|
9980
10115
|
continue;
|
|
9981
|
-
current =
|
|
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}${
|
|
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
|
|
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 =
|
|
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}${
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
10582
|
+
const target = path6.resolve(root, item);
|
|
10431
10583
|
if (!inside(root, target))
|
|
10432
|
-
return failure("invalid_input",
|
|
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 =
|
|
10587
|
+
ancestor = path6.dirname(ancestor);
|
|
10436
10588
|
try {
|
|
10437
10589
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10438
|
-
return failure("invalid_input",
|
|
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",
|
|
10592
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
10441
10593
|
}
|
|
10442
10594
|
roots.push(target);
|
|
10443
10595
|
}
|
|
@@ -10532,7 +10684,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
|
10532
10684
|
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10533
10685
|
header Workit decision: <purpose>, the same label repeated in the question
|
|
10534
10686
|
text (some host UIs do not render headers), exactly two options approved and
|
|
10535
|
-
rejected; ask once, never re-ask to mint a receipt.
|
|
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,
|
|
10536
10699
|
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10537
10700
|
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10538
10701
|
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
@@ -10558,7 +10721,7 @@ var init_methods = __esm(() => {
|
|
|
10558
10721
|
METHODS = {
|
|
10559
10722
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10560
10723
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10561
|
-
"workit-review": { dimensions: ["review"] },
|
|
10724
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
10562
10725
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10563
10726
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10564
10727
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -10576,7 +10739,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10576
10739
|
return candidate;
|
|
10577
10740
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10578
10741
|
const blockers = [...view.task.progress.blockers];
|
|
10579
|
-
if (view.task.workers.some((entry) =>
|
|
10742
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
10580
10743
|
blockers.push({
|
|
10581
10744
|
reason: "worker state requires reconciliation",
|
|
10582
10745
|
dependentAction: "resume",
|
|
@@ -10659,6 +10822,7 @@ var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, s
|
|
|
10659
10822
|
task: bundle.task
|
|
10660
10823
|
});
|
|
10661
10824
|
var init_task_context = __esm(() => {
|
|
10825
|
+
init_workers();
|
|
10662
10826
|
init_task_contract();
|
|
10663
10827
|
init_task_evaluation();
|
|
10664
10828
|
init_methods();
|
|
@@ -10687,7 +10851,11 @@ var init_external_action = __esm(() => {
|
|
|
10687
10851
|
}).strict(),
|
|
10688
10852
|
object({
|
|
10689
10853
|
operation: literal("git.commit"),
|
|
10690
|
-
payload: object({
|
|
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()
|
|
10691
10859
|
}).strict(),
|
|
10692
10860
|
object({
|
|
10693
10861
|
operation: literal("git.push"),
|
|
@@ -10819,7 +10987,7 @@ function resolveRequirements(input) {
|
|
|
10819
10987
|
].join(", ")}`,
|
|
10820
10988
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
10821
10989
|
before: "dependent_action",
|
|
10822
|
-
dependentAction:
|
|
10990
|
+
dependentAction: null,
|
|
10823
10991
|
acceptanceAllowed: false
|
|
10824
10992
|
}));
|
|
10825
10993
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -10830,7 +10998,7 @@ function resolveRequirements(input) {
|
|
|
10830
10998
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
10831
10999
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
10832
11000
|
before: "dependent_action",
|
|
10833
|
-
dependentAction:
|
|
11001
|
+
dependentAction: null,
|
|
10834
11002
|
acceptanceAllowed: false
|
|
10835
11003
|
}));
|
|
10836
11004
|
if (signals.behaviorChange.value === true)
|
|
@@ -11082,58 +11250,6 @@ var init_policy_resolver = __esm(() => {
|
|
|
11082
11250
|
}).strict();
|
|
11083
11251
|
});
|
|
11084
11252
|
|
|
11085
|
-
// packages/workit-core/src/core/workers.ts
|
|
11086
|
-
function assertProductWriteAllowed(input) {
|
|
11087
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
11088
|
-
return failure("invalid_input", "invalid product write path");
|
|
11089
|
-
if (!input.store)
|
|
11090
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
11091
|
-
const task = input.store.readTask(input.task.id);
|
|
11092
|
-
if (!task.ok)
|
|
11093
|
-
return task;
|
|
11094
|
-
const workspace = input.store.readWorkspace();
|
|
11095
|
-
if (!workspace.ok)
|
|
11096
|
-
return workspace;
|
|
11097
|
-
if (!workspace.data)
|
|
11098
|
-
return failure("not_found", "workspace not found");
|
|
11099
|
-
const workspaceRecord = workspace.data;
|
|
11100
|
-
if (task.data.status !== "active")
|
|
11101
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
11102
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
11103
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
11104
|
-
const writer = workspaceRecord.writer;
|
|
11105
|
-
if (!writer)
|
|
11106
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
11107
|
-
if (writer.state === "uncertain")
|
|
11108
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
11109
|
-
owner: writer.owner
|
|
11110
|
-
});
|
|
11111
|
-
const caller = callerValue(input.caller);
|
|
11112
|
-
const workerId = workerIdOf(input.caller);
|
|
11113
|
-
const owner = writer.owner;
|
|
11114
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
11115
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
11116
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
11117
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
11118
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
11119
|
-
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))))
|
|
11120
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
11121
|
-
return success(null, workspaceRecord.revision, owner);
|
|
11122
|
-
}
|
|
11123
|
-
var same = (left, right) => {
|
|
11124
|
-
try {
|
|
11125
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
11126
|
-
} catch {
|
|
11127
|
-
return false;
|
|
11128
|
-
}
|
|
11129
|
-
}, callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
11130
|
-
const code = char.charCodeAt(0);
|
|
11131
|
-
return code < 32 || code === 127;
|
|
11132
|
-
}) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
|
|
11133
|
-
var init_workers = __esm(() => {
|
|
11134
|
-
init_task_contract();
|
|
11135
|
-
});
|
|
11136
|
-
|
|
11137
11253
|
// packages/workit-core/src/core/task-engine.ts
|
|
11138
11254
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
11139
11255
|
|
|
@@ -11195,7 +11311,7 @@ class WorkitCore {
|
|
|
11195
11311
|
activeWorkerBlocker(task, workspace) {
|
|
11196
11312
|
if (workspace.writer)
|
|
11197
11313
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11198
|
-
if (task.workers.some((entry) =>
|
|
11314
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
11199
11315
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11200
11316
|
return null;
|
|
11201
11317
|
}
|
|
@@ -11432,11 +11548,6 @@ class WorkitCore {
|
|
|
11432
11548
|
if (helper && !helper.ok)
|
|
11433
11549
|
return helper;
|
|
11434
11550
|
const evidence = input.evidence;
|
|
11435
|
-
if (helper?.ok) {
|
|
11436
|
-
const assignment = helper.data.data.assignment;
|
|
11437
|
-
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))
|
|
11438
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11439
|
-
}
|
|
11440
11551
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
11441
11552
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
11442
11553
|
if (evidence.kind === "review") {
|
|
@@ -11453,6 +11564,11 @@ class WorkitCore {
|
|
|
11453
11564
|
evidence.beforeCandidateId ??= null;
|
|
11454
11565
|
evidence.candidateId ??= null;
|
|
11455
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
|
+
}
|
|
11456
11572
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11457
11573
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
11458
11574
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -12348,6 +12464,9 @@ class WorkitCore {
|
|
|
12348
12464
|
id: task.id,
|
|
12349
12465
|
revision: task.revision,
|
|
12350
12466
|
workspaceRevision: workspace.data.revision,
|
|
12467
|
+
createdAt: task.createdAt,
|
|
12468
|
+
updatedAt: task.updatedAt,
|
|
12469
|
+
runtime: task.runtime ?? null,
|
|
12351
12470
|
objective: task.intent.data.objective,
|
|
12352
12471
|
status: task.status,
|
|
12353
12472
|
closure: task.closure,
|
|
@@ -12433,7 +12552,7 @@ class WorkitCore {
|
|
|
12433
12552
|
const base = reconcileResume(effectiveView);
|
|
12434
12553
|
if (!base.ok)
|
|
12435
12554
|
return base;
|
|
12436
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
12555
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
12437
12556
|
const blockers = [...base.data.blockers];
|
|
12438
12557
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
12439
12558
|
blockers.push({
|
|
@@ -12500,7 +12619,8 @@ class WorkitCore {
|
|
|
12500
12619
|
...current,
|
|
12501
12620
|
status,
|
|
12502
12621
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
12503
|
-
progress:
|
|
12622
|
+
progress: current.progress,
|
|
12623
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
12504
12624
|
})
|
|
12505
12625
|
});
|
|
12506
12626
|
if (!changed.ok)
|
|
@@ -12536,7 +12656,7 @@ class WorkitCore {
|
|
|
12536
12656
|
return workspace;
|
|
12537
12657
|
if (!workspace.data)
|
|
12538
12658
|
return failure("not_found", "workspace not found");
|
|
12539
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
12659
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12540
12660
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
12541
12661
|
this.fillRevisions(input, task.data, workspace.data);
|
|
12542
12662
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -12736,15 +12856,20 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
|
|
|
12736
12856
|
return failure("invalid_transition", "worker has not started");
|
|
12737
12857
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
12738
12858
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
12859
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
12739
12860
|
const nextEntry = {
|
|
12740
12861
|
...entry,
|
|
12741
12862
|
recordedAt: input.now ?? entry.recordedAt,
|
|
12742
12863
|
provenance: authority.provenance,
|
|
12743
|
-
data: {
|
|
12864
|
+
data: {
|
|
12865
|
+
...entry.data,
|
|
12866
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
12867
|
+
session: input.session
|
|
12868
|
+
}
|
|
12744
12869
|
};
|
|
12745
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;
|
|
12746
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;
|
|
12747
|
-
if (entry.data.state ===
|
|
12872
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
12748
12873
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
12749
12874
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
12750
12875
|
taskId: task.data.id,
|
|
@@ -13072,8 +13197,8 @@ var init_core3 = __esm(() => {
|
|
|
13072
13197
|
});
|
|
13073
13198
|
|
|
13074
13199
|
// packages/workit-cursor/hooks/workit-hook.ts
|
|
13075
|
-
import { existsSync as
|
|
13076
|
-
import
|
|
13200
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
13201
|
+
import path7 from "node:path";
|
|
13077
13202
|
var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapabilities = (availability = {}) => {
|
|
13078
13203
|
const has = (name) => availability[name] === true;
|
|
13079
13204
|
return [
|
|
@@ -13105,6 +13230,13 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13105
13230
|
reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
|
|
13106
13231
|
refs: [hostRef("subagentStart")]
|
|
13107
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
|
+
},
|
|
13108
13240
|
{
|
|
13109
13241
|
name: "arbitrary_shell_write",
|
|
13110
13242
|
surface: "unobservable_shell",
|
|
@@ -13127,7 +13259,7 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13127
13259
|
if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
|
|
13128
13260
|
return { ok: false, error: "exactly one workspace root is required" };
|
|
13129
13261
|
const root = roots[0];
|
|
13130
|
-
if (!
|
|
13262
|
+
if (!path7.isAbsolute(root) || !existsSync4(root))
|
|
13131
13263
|
return { ok: false, error: "workspace root must be an existing absolute path" };
|
|
13132
13264
|
const event = value.hook_event_name;
|
|
13133
13265
|
const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
|