@brainervirus/workit-cursor 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +292 -152
- package/dist/mcp-server.js +366 -222
- package/dist/workit-hook.js +291 -151
- 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,10 +7794,12 @@ var init_task_contract = __esm(() => {
|
|
|
7794
7794
|
scope: scopeSchema,
|
|
7795
7795
|
presented: text,
|
|
7796
7796
|
approvedContent: text,
|
|
7797
|
-
|
|
7797
|
+
displayed: text.optional(),
|
|
7798
|
+
contentRefs: array(refSchema),
|
|
7799
|
+
statedChoice: object({ ref: text, text }).strict().optional()
|
|
7798
7800
|
}).strict(),
|
|
7799
7801
|
digest,
|
|
7800
|
-
response: _enum(["approved", "rejected"]),
|
|
7802
|
+
response: _enum(["approved", "rejected", "stated"]),
|
|
7801
7803
|
requirementIds: array(digest),
|
|
7802
7804
|
revoked: object({ at: utc, reason: text }).strict().nullable(),
|
|
7803
7805
|
consumption: object({
|
|
@@ -7872,6 +7874,10 @@ var init_task_contract = __esm(() => {
|
|
|
7872
7874
|
path: ["decisionId"]
|
|
7873
7875
|
});
|
|
7874
7876
|
});
|
|
7877
|
+
runtimeSchema = object({
|
|
7878
|
+
createdWith: text.nullable(),
|
|
7879
|
+
updatedWith: text
|
|
7880
|
+
}).strict();
|
|
7875
7881
|
taskRecordSchema = object({
|
|
7876
7882
|
schemaVersion: literal(1),
|
|
7877
7883
|
id,
|
|
@@ -7885,6 +7891,8 @@ var init_task_contract = __esm(() => {
|
|
|
7885
7891
|
status: _enum(["active", "paused", "closed"]),
|
|
7886
7892
|
closure: closureSchema.nullable(),
|
|
7887
7893
|
progress: progressSchema,
|
|
7894
|
+
pauseReason: text.nullable().optional(),
|
|
7895
|
+
runtime: runtimeSchema.optional(),
|
|
7888
7896
|
assessments: array(entrySchema(assessmentSchema)),
|
|
7889
7897
|
policy: policySchema.nullable(),
|
|
7890
7898
|
policyChanges: array(policyChangeSchema),
|
|
@@ -7900,6 +7908,7 @@ var init_task_contract = __esm(() => {
|
|
|
7900
7908
|
id,
|
|
7901
7909
|
revision,
|
|
7902
7910
|
root: nonEmpty,
|
|
7911
|
+
runtime: runtimeSchema.optional(),
|
|
7903
7912
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
7904
7913
|
}).strict();
|
|
7905
7914
|
capabilitySchema = object({
|
|
@@ -7927,6 +7936,9 @@ var init_task_contract = __esm(() => {
|
|
|
7927
7936
|
id,
|
|
7928
7937
|
revision,
|
|
7929
7938
|
workspaceRevision: revision,
|
|
7939
|
+
createdAt: utc,
|
|
7940
|
+
updatedAt: utc,
|
|
7941
|
+
runtime: runtimeSchema.nullable(),
|
|
7930
7942
|
objective: text,
|
|
7931
7943
|
status: _enum(["active", "paused", "closed"]),
|
|
7932
7944
|
closure: closureSchema.nullable(),
|
|
@@ -7996,7 +8008,7 @@ var init_task_contract = __esm(() => {
|
|
|
7996
8008
|
expectedWorkspaceRevision: revision.optional(),
|
|
7997
8009
|
outcome: outcomeSchema,
|
|
7998
8010
|
summary: text,
|
|
7999
|
-
decisionIds: array(id)
|
|
8011
|
+
decisionIds: array(id).optional()
|
|
8000
8012
|
})
|
|
8001
8013
|
};
|
|
8002
8014
|
policyOperations = {
|
|
@@ -8088,14 +8100,15 @@ var init_task_contract = __esm(() => {
|
|
|
8088
8100
|
...taskId,
|
|
8089
8101
|
expectedRevision: revision.optional(),
|
|
8090
8102
|
expectedWorkspaceRevision: revision.optional(),
|
|
8091
|
-
workerId: nullableId.optional()
|
|
8103
|
+
workerId: nullableId.optional(),
|
|
8104
|
+
reason: text.optional()
|
|
8092
8105
|
}),
|
|
8093
8106
|
release: operation({
|
|
8094
8107
|
action: literal("release"),
|
|
8095
8108
|
...taskId,
|
|
8096
8109
|
expectedRevision: revision.optional(),
|
|
8097
8110
|
expectedWorkspaceRevision: revision.optional(),
|
|
8098
|
-
reason: text
|
|
8111
|
+
reason: text.optional()
|
|
8099
8112
|
})
|
|
8100
8113
|
};
|
|
8101
8114
|
stateOperations = {
|
|
@@ -8130,6 +8143,21 @@ var init_task_contract = __esm(() => {
|
|
|
8130
8143
|
compiledOperationSchemas = Object.fromEntries(OPERATION_FAMILIES.map((family) => [family, compile(operationSchemas[family])]));
|
|
8131
8144
|
});
|
|
8132
8145
|
|
|
8146
|
+
// packages/workit-core/src/core/package-root.ts
|
|
8147
|
+
import { existsSync } from "node:fs";
|
|
8148
|
+
import path from "node:path";
|
|
8149
|
+
import { fileURLToPath } from "node:url";
|
|
8150
|
+
var packageRoot = () => {
|
|
8151
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
8152
|
+
while (true) {
|
|
8153
|
+
const parent = path.dirname(dir);
|
|
8154
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
8155
|
+
return dir;
|
|
8156
|
+
dir = parent;
|
|
8157
|
+
}
|
|
8158
|
+
};
|
|
8159
|
+
var init_package_root = () => {};
|
|
8160
|
+
|
|
8133
8161
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
8134
8162
|
function categorizeFsSafeError(code) {
|
|
8135
8163
|
return OPERATIONAL_CODES.has(code) ? "operational" : "policy";
|
|
@@ -8240,7 +8268,7 @@ var init_read_open_flags = () => {};
|
|
|
8240
8268
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
8241
8269
|
import { randomBytes } from "node:crypto";
|
|
8242
8270
|
import fsSync from "node:fs";
|
|
8243
|
-
import
|
|
8271
|
+
import path2 from "node:path";
|
|
8244
8272
|
function parseSidecarLockSnapshot(snapshot, parser) {
|
|
8245
8273
|
return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
|
|
8246
8274
|
}
|
|
@@ -8268,13 +8296,13 @@ ${ownershipToken}
|
|
|
8268
8296
|
return { raw, ownershipToken };
|
|
8269
8297
|
}
|
|
8270
8298
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
8271
|
-
const resolved =
|
|
8272
|
-
const lexicalRelative =
|
|
8273
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8274
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
8299
|
+
const resolved = path2.resolve(lockPath);
|
|
8300
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
8301
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
8302
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
8275
8303
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
8276
8304
|
}
|
|
8277
|
-
return relative.split(
|
|
8305
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
8278
8306
|
}
|
|
8279
8307
|
function parseSidecarLockPayload(raw, parser) {
|
|
8280
8308
|
if (parser) {
|
|
@@ -8444,7 +8472,7 @@ function sleepSync(ms) {
|
|
|
8444
8472
|
|
|
8445
8473
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
8446
8474
|
import fs3 from "node:fs";
|
|
8447
|
-
import
|
|
8475
|
+
import path3 from "node:path";
|
|
8448
8476
|
function getSyncHeldLocks() {
|
|
8449
8477
|
const globalWithState = globalThis;
|
|
8450
8478
|
if (!globalWithState[SYNC_HELD_LOCKS_KEY]) {
|
|
@@ -8522,26 +8550,26 @@ function canonicalLockParentSync(parent) {
|
|
|
8522
8550
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
8523
8551
|
}
|
|
8524
8552
|
function normalizeTargetPath(targetPath) {
|
|
8525
|
-
const resolved =
|
|
8526
|
-
fs3.mkdirSync(
|
|
8553
|
+
const resolved = path3.resolve(targetPath);
|
|
8554
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8527
8555
|
try {
|
|
8528
|
-
return
|
|
8556
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
8529
8557
|
} catch {
|
|
8530
8558
|
return resolved;
|
|
8531
8559
|
}
|
|
8532
8560
|
}
|
|
8533
8561
|
function boundedLockPath(lockPath, lockRoot) {
|
|
8534
|
-
const resolved =
|
|
8562
|
+
const resolved = path3.resolve(lockPath);
|
|
8535
8563
|
if (!lockRoot)
|
|
8536
8564
|
return resolved;
|
|
8537
8565
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8538
|
-
const parent =
|
|
8566
|
+
const parent = path3.dirname(resolved);
|
|
8539
8567
|
const parentReal = canonicalLockParentSync(parent);
|
|
8540
|
-
const parentRelative =
|
|
8541
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
8568
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
8569
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
8542
8570
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
8543
8571
|
}
|
|
8544
|
-
return
|
|
8572
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
8545
8573
|
}
|
|
8546
8574
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
8547
8575
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -8760,17 +8788,17 @@ var init_file_lock = __esm(() => {
|
|
|
8760
8788
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
8761
8789
|
import * as fs4 from "node:fs";
|
|
8762
8790
|
import { hostname } from "node:os";
|
|
8763
|
-
import
|
|
8791
|
+
import path4 from "node:path";
|
|
8764
8792
|
|
|
8765
8793
|
class TaskStore {
|
|
8766
8794
|
root;
|
|
8767
8795
|
constructor(root) {
|
|
8768
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
8796
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
8769
8797
|
}
|
|
8770
8798
|
readTask(taskId) {
|
|
8771
8799
|
if (!validId(taskId))
|
|
8772
8800
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
8773
|
-
const result = this.readRecord(
|
|
8801
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
8774
8802
|
if (!result.exists)
|
|
8775
8803
|
return failure("not_found", "task not found", { taskId });
|
|
8776
8804
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -8804,7 +8832,7 @@ class TaskStore {
|
|
|
8804
8832
|
});
|
|
8805
8833
|
const tasks = [];
|
|
8806
8834
|
for (const name of names.sort()) {
|
|
8807
|
-
const item = this.readRecord(
|
|
8835
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
8808
8836
|
if (!item.exists)
|
|
8809
8837
|
continue;
|
|
8810
8838
|
if (!item.result.ok)
|
|
@@ -8838,7 +8866,7 @@ class TaskStore {
|
|
|
8838
8866
|
if (!current.ok)
|
|
8839
8867
|
return current;
|
|
8840
8868
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
8841
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8869
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8842
8870
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
8843
8871
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8844
8872
|
});
|
|
@@ -8850,11 +8878,17 @@ class TaskStore {
|
|
|
8850
8878
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
8851
8879
|
if (current.data && !previousWorkspaceBytes)
|
|
8852
8880
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
8853
|
-
const workspace = current.data ? {
|
|
8881
|
+
const workspace = current.data ? {
|
|
8882
|
+
...current.data,
|
|
8883
|
+
revision: newRevision(),
|
|
8884
|
+
root: this.root,
|
|
8885
|
+
runtime: stampNew(current.data)
|
|
8886
|
+
} : {
|
|
8854
8887
|
schemaVersion: SCHEMA_VERSION,
|
|
8855
8888
|
id: newId(),
|
|
8856
8889
|
revision: newRevision(),
|
|
8857
8890
|
root: this.root,
|
|
8891
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8858
8892
|
writer: null
|
|
8859
8893
|
};
|
|
8860
8894
|
const timestamp = value.now ?? now();
|
|
@@ -8876,6 +8910,7 @@ class TaskStore {
|
|
|
8876
8910
|
status: "active",
|
|
8877
8911
|
closure: null,
|
|
8878
8912
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
8913
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8879
8914
|
assessments: [],
|
|
8880
8915
|
policy: null,
|
|
8881
8916
|
policyChanges: [],
|
|
@@ -8906,7 +8941,7 @@ class TaskStore {
|
|
|
8906
8941
|
if (!current.ok)
|
|
8907
8942
|
return current;
|
|
8908
8943
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
8909
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8944
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8910
8945
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
8911
8946
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8912
8947
|
});
|
|
@@ -8914,11 +8949,17 @@ class TaskStore {
|
|
|
8914
8949
|
if (current.data && !previousWorkspaceBytes)
|
|
8915
8950
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
8916
8951
|
const timestamp = input.now ?? now();
|
|
8917
|
-
const workspace = current.data ? {
|
|
8952
|
+
const workspace = current.data ? {
|
|
8953
|
+
...current.data,
|
|
8954
|
+
revision: newRevision(),
|
|
8955
|
+
root: this.root,
|
|
8956
|
+
runtime: stampNew(current.data)
|
|
8957
|
+
} : {
|
|
8918
8958
|
schemaVersion: SCHEMA_VERSION,
|
|
8919
8959
|
id: input.workspaceId ?? newId(),
|
|
8920
8960
|
revision: newRevision(),
|
|
8921
8961
|
root: this.root,
|
|
8962
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8922
8963
|
writer: null
|
|
8923
8964
|
};
|
|
8924
8965
|
const task = {
|
|
@@ -8926,7 +8967,11 @@ class TaskStore {
|
|
|
8926
8967
|
workspaceId: workspace.id,
|
|
8927
8968
|
revision: newRevision(),
|
|
8928
8969
|
createdAt: timestamp,
|
|
8929
|
-
updatedAt: timestamp
|
|
8970
|
+
updatedAt: timestamp,
|
|
8971
|
+
runtime: {
|
|
8972
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
8973
|
+
updatedWith: runtimeVersion()
|
|
8974
|
+
}
|
|
8930
8975
|
};
|
|
8931
8976
|
const validTask = taskRecordSchema.safeParse(task);
|
|
8932
8977
|
if (!validTask.success)
|
|
@@ -8975,7 +9020,8 @@ class TaskStore {
|
|
|
8975
9020
|
workspaceId: current.data.workspaceId,
|
|
8976
9021
|
createdAt: current.data.createdAt,
|
|
8977
9022
|
revision: context.revision,
|
|
8978
|
-
updatedAt: context.now
|
|
9023
|
+
updatedAt: context.now,
|
|
9024
|
+
runtime: stampNew(current.data)
|
|
8979
9025
|
};
|
|
8980
9026
|
const valid = taskRecordSchema.safeParse(record);
|
|
8981
9027
|
if (!valid.success)
|
|
@@ -9009,7 +9055,8 @@ class TaskStore {
|
|
|
9009
9055
|
...changed.data,
|
|
9010
9056
|
id: current.data.id,
|
|
9011
9057
|
root: this.root,
|
|
9012
|
-
revision: context.revision
|
|
9058
|
+
revision: context.revision,
|
|
9059
|
+
runtime: stampNew(current.data)
|
|
9013
9060
|
};
|
|
9014
9061
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
9015
9062
|
if (!valid.success)
|
|
@@ -9052,7 +9099,8 @@ class TaskStore {
|
|
|
9052
9099
|
...changedWorkspace.data,
|
|
9053
9100
|
id: workspace.data.id,
|
|
9054
9101
|
root: this.root,
|
|
9055
|
-
revision: workspaceContext.revision
|
|
9102
|
+
revision: workspaceContext.revision,
|
|
9103
|
+
runtime: stampNew(workspace.data)
|
|
9056
9104
|
});
|
|
9057
9105
|
if (!nextWorkspace.success)
|
|
9058
9106
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -9077,7 +9125,8 @@ class TaskStore {
|
|
|
9077
9125
|
workspaceId: task.data.workspaceId,
|
|
9078
9126
|
createdAt: task.data.createdAt,
|
|
9079
9127
|
revision: taskContext.revision,
|
|
9080
|
-
updatedAt: taskContext.now
|
|
9128
|
+
updatedAt: taskContext.now,
|
|
9129
|
+
runtime: stampNew(task.data)
|
|
9081
9130
|
});
|
|
9082
9131
|
if (!nextTask.success) {
|
|
9083
9132
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -9104,7 +9153,7 @@ class TaskStore {
|
|
|
9104
9153
|
if (match)
|
|
9105
9154
|
candidates.push({
|
|
9106
9155
|
target: match[1],
|
|
9107
|
-
path:
|
|
9156
|
+
path: path4.join(this.recoveryDir, name),
|
|
9108
9157
|
digest: match[3]
|
|
9109
9158
|
});
|
|
9110
9159
|
}
|
|
@@ -9297,7 +9346,8 @@ class TaskStore {
|
|
|
9297
9346
|
const value = {
|
|
9298
9347
|
...workspace,
|
|
9299
9348
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
9300
|
-
revision: newRevision()
|
|
9349
|
+
revision: newRevision(),
|
|
9350
|
+
runtime: stampNew(workspace)
|
|
9301
9351
|
};
|
|
9302
9352
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
9303
9353
|
}
|
|
@@ -9375,7 +9425,7 @@ class TaskStore {
|
|
|
9375
9425
|
}
|
|
9376
9426
|
fs4.renameSync(temporary, file);
|
|
9377
9427
|
temporary = undefined;
|
|
9378
|
-
this.fsyncDirectory(
|
|
9428
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
9379
9429
|
return success(null, null, value);
|
|
9380
9430
|
} catch (error) {
|
|
9381
9431
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -9389,9 +9439,9 @@ class TaskStore {
|
|
|
9389
9439
|
}
|
|
9390
9440
|
}
|
|
9391
9441
|
saveRecovery(file, bytes) {
|
|
9392
|
-
const target =
|
|
9393
|
-
const id = target === "task" ?
|
|
9394
|
-
const destination =
|
|
9442
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
9443
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
9444
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
9395
9445
|
if (fs4.existsSync(destination)) {
|
|
9396
9446
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
9397
9447
|
return;
|
|
@@ -9469,7 +9519,7 @@ class TaskStore {
|
|
|
9469
9519
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
9470
9520
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
9471
9521
|
return null;
|
|
9472
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
9522
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
9473
9523
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
9474
9524
|
return null;
|
|
9475
9525
|
ids.add(parsed.data.workspaceId);
|
|
@@ -9511,7 +9561,12 @@ class TaskStore {
|
|
|
9511
9561
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
9512
9562
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
9513
9563
|
const parsed = schema.safeParse(value);
|
|
9514
|
-
|
|
9564
|
+
if (parsed.success)
|
|
9565
|
+
return success(null, null, parsed.data);
|
|
9566
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
9567
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
9568
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
9569
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
9515
9570
|
}
|
|
9516
9571
|
processStart(pid) {
|
|
9517
9572
|
try {
|
|
@@ -9521,34 +9576,57 @@ class TaskStore {
|
|
|
9521
9576
|
}
|
|
9522
9577
|
}
|
|
9523
9578
|
conflict(expected, actual) {
|
|
9524
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
9579
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
9525
9580
|
expectedRevision: expected,
|
|
9526
9581
|
actualRevision: actual
|
|
9527
9582
|
});
|
|
9528
9583
|
}
|
|
9529
9584
|
taskPath(taskId) {
|
|
9530
|
-
return
|
|
9585
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9531
9586
|
}
|
|
9532
9587
|
get workitDir() {
|
|
9533
|
-
return
|
|
9588
|
+
return path4.join(this.root, ".workit");
|
|
9534
9589
|
}
|
|
9535
9590
|
get tasksDir() {
|
|
9536
|
-
return
|
|
9591
|
+
return path4.join(this.workitDir, "tasks");
|
|
9537
9592
|
}
|
|
9538
9593
|
get recoveryDir() {
|
|
9539
|
-
return
|
|
9594
|
+
return path4.join(this.workitDir, "recovery");
|
|
9540
9595
|
}
|
|
9541
9596
|
get workspacePath() {
|
|
9542
|
-
return
|
|
9597
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9543
9598
|
}
|
|
9544
9599
|
get lockPath() {
|
|
9545
|
-
return
|
|
9600
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9546
9601
|
}
|
|
9547
9602
|
get gitignorePath() {
|
|
9548
|
-
return
|
|
9603
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
9549
9604
|
}
|
|
9550
9605
|
}
|
|
9551
|
-
var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
9606
|
+
var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), cachedRuntimeVersion = null, runtimeVersion = () => {
|
|
9607
|
+
if (cachedRuntimeVersion === null) {
|
|
9608
|
+
try {
|
|
9609
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
9610
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
9611
|
+
} catch {
|
|
9612
|
+
cachedRuntimeVersion = "0.0.0";
|
|
9613
|
+
}
|
|
9614
|
+
}
|
|
9615
|
+
return cachedRuntimeVersion;
|
|
9616
|
+
}, stampNew = (record) => ({
|
|
9617
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
9618
|
+
updatedWith: runtimeVersion()
|
|
9619
|
+
}), isNewerVersion = (candidate, current) => {
|
|
9620
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
9621
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
9622
|
+
for (let index = 0;index < 3; index += 1) {
|
|
9623
|
+
const left = next[index] ?? 0;
|
|
9624
|
+
const right = now[index] ?? 0;
|
|
9625
|
+
if (left !== right)
|
|
9626
|
+
return left > right;
|
|
9627
|
+
}
|
|
9628
|
+
return false;
|
|
9629
|
+
}, jsonBytes = (value) => `${canonicalJson(value)}
|
|
9552
9630
|
`, 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
9631
|
let value;
|
|
9554
9632
|
try {
|
|
@@ -9566,6 +9644,7 @@ var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOStrin
|
|
|
9566
9644
|
};
|
|
9567
9645
|
var init_task_store = __esm(() => {
|
|
9568
9646
|
init_zod();
|
|
9647
|
+
init_package_root();
|
|
9569
9648
|
init_file_lock();
|
|
9570
9649
|
init_task_contract();
|
|
9571
9650
|
processEvidenceSchema = object({
|
|
@@ -9582,10 +9661,63 @@ var init_task_store = __esm(() => {
|
|
|
9582
9661
|
}).strict();
|
|
9583
9662
|
});
|
|
9584
9663
|
|
|
9664
|
+
// packages/workit-core/src/core/workers.ts
|
|
9665
|
+
function assertProductWriteAllowed(input) {
|
|
9666
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
9667
|
+
return failure("invalid_input", "invalid product write path");
|
|
9668
|
+
if (!input.store)
|
|
9669
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
9670
|
+
const task = input.store.readTask(input.task.id);
|
|
9671
|
+
if (!task.ok)
|
|
9672
|
+
return task;
|
|
9673
|
+
const workspace = input.store.readWorkspace();
|
|
9674
|
+
if (!workspace.ok)
|
|
9675
|
+
return workspace;
|
|
9676
|
+
if (!workspace.data)
|
|
9677
|
+
return failure("not_found", "workspace not found");
|
|
9678
|
+
const workspaceRecord = workspace.data;
|
|
9679
|
+
if (task.data.status !== "active")
|
|
9680
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
9681
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
9682
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
9683
|
+
const writer = workspaceRecord.writer;
|
|
9684
|
+
if (!writer)
|
|
9685
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
9686
|
+
if (writer.state === "uncertain")
|
|
9687
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
9688
|
+
owner: writer.owner
|
|
9689
|
+
});
|
|
9690
|
+
const caller = callerValue(input.caller);
|
|
9691
|
+
const workerId = workerIdOf(input.caller);
|
|
9692
|
+
const owner = writer.owner;
|
|
9693
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
9694
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
9695
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
9696
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
9697
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
9698
|
+
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))))
|
|
9699
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
9700
|
+
return success(null, workspaceRecord.revision, owner);
|
|
9701
|
+
}
|
|
9702
|
+
var same = (left, right) => {
|
|
9703
|
+
try {
|
|
9704
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
9705
|
+
} catch {
|
|
9706
|
+
return false;
|
|
9707
|
+
}
|
|
9708
|
+
}, 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) => {
|
|
9709
|
+
const code = char.charCodeAt(0);
|
|
9710
|
+
return code < 32 || code === 127;
|
|
9711
|
+
}) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
|
|
9712
|
+
var init_workers = __esm(() => {
|
|
9713
|
+
init_task_contract();
|
|
9714
|
+
UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9715
|
+
});
|
|
9716
|
+
|
|
9585
9717
|
// packages/workit-core/src/core/authority.ts
|
|
9586
9718
|
import { createHash as createHash3 } from "node:crypto";
|
|
9587
9719
|
import * as fs5 from "node:fs";
|
|
9588
|
-
import
|
|
9720
|
+
import path5 from "node:path";
|
|
9589
9721
|
function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
9590
9722
|
if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
|
|
9591
9723
|
return [];
|
|
@@ -9969,20 +10101,31 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
|
|
|
9969
10101
|
continue;
|
|
9970
10102
|
if (!reference.digest)
|
|
9971
10103
|
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"
|
|
10104
|
+
const target = path5.resolve(root, reference.path);
|
|
10105
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
10106
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10107
|
+
fields: [
|
|
10108
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
10109
|
+
]
|
|
10110
|
+
});
|
|
9975
10111
|
try {
|
|
9976
|
-
const relative =
|
|
10112
|
+
const relative = path5.relative(root, target);
|
|
9977
10113
|
let current = root;
|
|
9978
|
-
for (const segment of relative.split(
|
|
10114
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9979
10115
|
if (!segment)
|
|
9980
10116
|
continue;
|
|
9981
|
-
current =
|
|
10117
|
+
current = path5.join(current, segment);
|
|
9982
10118
|
const stat = fs5.lstatSync(current);
|
|
9983
10119
|
const real = fs5.realpathSync(current);
|
|
9984
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
9985
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
10120
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
10121
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10122
|
+
fields: [
|
|
10123
|
+
{
|
|
10124
|
+
path: "contentRefs.path",
|
|
10125
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
10126
|
+
}
|
|
10127
|
+
]
|
|
10128
|
+
});
|
|
9986
10129
|
if (stat.isSymbolicLink())
|
|
9987
10130
|
return failure("invalid_input", "document reference uses a symlink");
|
|
9988
10131
|
if (current === target && !stat.isFile())
|
|
@@ -10059,7 +10202,7 @@ var init_authority = __esm(() => {
|
|
|
10059
10202
|
import { createHash as createHash4 } from "node:crypto";
|
|
10060
10203
|
import * as fs6 from "node:fs";
|
|
10061
10204
|
import { spawnSync } from "node:child_process";
|
|
10062
|
-
import
|
|
10205
|
+
import path6 from "node:path";
|
|
10063
10206
|
function captureCandidate(root, scope, environment = []) {
|
|
10064
10207
|
let checkout;
|
|
10065
10208
|
try {
|
|
@@ -10069,7 +10212,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10069
10212
|
}
|
|
10070
10213
|
const normalizedScope = canonicalScope(scope);
|
|
10071
10214
|
if (!normalizedScope)
|
|
10072
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
10215
|
+
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
10216
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
10074
10217
|
if (!roots.ok)
|
|
10075
10218
|
return roots;
|
|
@@ -10101,7 +10244,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10101
10244
|
for (const item of [...names].sort()) {
|
|
10102
10245
|
if (!scopeMatches(item, normalizedScope))
|
|
10103
10246
|
continue;
|
|
10104
|
-
const target =
|
|
10247
|
+
const target = path6.join(checkout, item);
|
|
10105
10248
|
try {
|
|
10106
10249
|
const stat = fs6.lstatSync(target);
|
|
10107
10250
|
if (stat.isSymbolicLink()) {
|
|
@@ -10220,7 +10363,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10220
10363
|
return true;
|
|
10221
10364
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10222
10365
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10223
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
10366
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
10224
10367
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10225
10368
|
});
|
|
10226
10369
|
if (passed.length) {
|
|
@@ -10261,7 +10404,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10261
10404
|
status: "satisfied",
|
|
10262
10405
|
evidenceIds: [],
|
|
10263
10406
|
decisionIds: decisions.map((decision) => decision.id),
|
|
10264
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
10407
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
10265
10408
|
};
|
|
10266
10409
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
10267
10410
|
if (limitations.length)
|
|
@@ -10316,7 +10459,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10316
10459
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
10317
10460
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10318
10461
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10319
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
10462
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
10463
|
+
requirements: blocking.map((item) => {
|
|
10464
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
10465
|
+
return {
|
|
10466
|
+
requirementId: item.requirementId,
|
|
10467
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
10468
|
+
reason: item.reason,
|
|
10469
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
10470
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
10471
|
+
};
|
|
10472
|
+
})
|
|
10320
10473
|
});
|
|
10321
10474
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
10322
10475
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -10342,12 +10495,12 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10342
10495
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
10343
10496
|
});
|
|
10344
10497
|
}
|
|
10345
|
-
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10498
|
+
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
10499
|
if (typeof value !== "string" || !value)
|
|
10347
10500
|
return null;
|
|
10348
10501
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
10349
10502
|
return null;
|
|
10350
|
-
const normalized =
|
|
10503
|
+
const normalized = path6.posix.normalize(value);
|
|
10351
10504
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
10352
10505
|
return null;
|
|
10353
10506
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10389,14 +10542,14 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10389
10542
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10390
10543
|
};
|
|
10391
10544
|
}
|
|
10392
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10545
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
10393
10546
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10394
10547
|
if (stagedDeleted.status !== 0) {
|
|
10395
10548
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10396
10549
|
if (!/not a git repository/i.test(stderr))
|
|
10397
10550
|
return { paths, uncertain: true, git: true };
|
|
10398
10551
|
} else if (stagedDeleted.stdout) {
|
|
10399
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10552
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
10400
10553
|
}
|
|
10401
10554
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10402
10555
|
}, walk = (root, directory, output) => {
|
|
@@ -10410,7 +10563,7 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10410
10563
|
for (const name of names) {
|
|
10411
10564
|
if (name === ".git" || name === ".workit")
|
|
10412
10565
|
continue;
|
|
10413
|
-
const target =
|
|
10566
|
+
const target = path6.join(directory, name);
|
|
10414
10567
|
let stat;
|
|
10415
10568
|
try {
|
|
10416
10569
|
stat = fs6.lstatSync(target);
|
|
@@ -10427,17 +10580,17 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10427
10580
|
}, scopeRoots = (root, scope) => {
|
|
10428
10581
|
const roots = [];
|
|
10429
10582
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10430
|
-
const target =
|
|
10583
|
+
const target = path6.resolve(root, item);
|
|
10431
10584
|
if (!inside(root, target))
|
|
10432
|
-
return failure("invalid_input",
|
|
10585
|
+
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
10586
|
let ancestor = target;
|
|
10434
10587
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10435
|
-
ancestor =
|
|
10588
|
+
ancestor = path6.dirname(ancestor);
|
|
10436
10589
|
try {
|
|
10437
10590
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10438
|
-
return failure("invalid_input",
|
|
10591
|
+
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
10592
|
} catch {
|
|
10440
|
-
return failure("invalid_input",
|
|
10593
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
10441
10594
|
}
|
|
10442
10595
|
roots.push(target);
|
|
10443
10596
|
}
|
|
@@ -10459,7 +10612,7 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10459
10612
|
}, sameSession = (left, right) => {
|
|
10460
10613
|
const other = sessionFromRef(right);
|
|
10461
10614
|
return left !== null && other !== null && left.host === other.host && left.handle === other.handle;
|
|
10462
|
-
}, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file"))), applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id })), evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10615
|
+
}, evidenceScopes = (task, ids) => (task.policy?.requirements ?? []).filter((requirement) => ids.includes(requirement.id)).map((requirement) => requirement.scope), scopeCovers2, applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file"))), applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id })), evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10463
10616
|
if (kind !== "passed")
|
|
10464
10617
|
return false;
|
|
10465
10618
|
if (dimension === "testing" || dimension === "verification")
|
|
@@ -10532,7 +10685,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
|
10532
10685
|
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10533
10686
|
header Workit decision: <purpose>, the same label repeated in the question
|
|
10534
10687
|
text (some host UIs do not render headers), exactly two options approved and
|
|
10535
|
-
rejected; ask once, never re-ask to mint a receipt.
|
|
10688
|
+
rejected; ask once, never re-ask to mint a receipt. Before asking, present the
|
|
10689
|
+
item in the conversation (durable artifacts: complete digest plus exact path;
|
|
10690
|
+
inline plans: the content) and keep the question to one short scoped sentence;
|
|
10691
|
+
never ask to approve something unseen. An answer with custom text is steering,
|
|
10692
|
+
not approval: adjust, re-present, then ask a new scoped question. After the user
|
|
10693
|
+
approves a spec or plan, execute the remaining tasks continuously with one
|
|
10694
|
+
atomic commit per task and do not ask whether to continue; stop only for a new
|
|
10695
|
+
product decision, a failed safety or verification gate, a conflicting edit, or
|
|
10696
|
+
missing authority. Record the plan's commit list once through the plan-scoped
|
|
10697
|
+
action approval (git.commit with plan_steps and plan_branch) so each listed
|
|
10698
|
+
commit executes without a new question; an unlisted message needs a fresh
|
|
10699
|
+
approval. task.close {outcome,
|
|
10536
10700
|
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10537
10701
|
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10538
10702
|
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
@@ -10558,7 +10722,7 @@ var init_methods = __esm(() => {
|
|
|
10558
10722
|
METHODS = {
|
|
10559
10723
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10560
10724
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10561
|
-
"workit-review": { dimensions: ["review"] },
|
|
10725
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
10562
10726
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10563
10727
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10564
10728
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -10576,7 +10740,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10576
10740
|
return candidate;
|
|
10577
10741
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10578
10742
|
const blockers = [...view.task.progress.blockers];
|
|
10579
|
-
if (view.task.workers.some((entry) =>
|
|
10743
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
10580
10744
|
blockers.push({
|
|
10581
10745
|
reason: "worker state requires reconciliation",
|
|
10582
10746
|
dependentAction: "resume",
|
|
@@ -10659,6 +10823,7 @@ var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, s
|
|
|
10659
10823
|
task: bundle.task
|
|
10660
10824
|
});
|
|
10661
10825
|
var init_task_context = __esm(() => {
|
|
10826
|
+
init_workers();
|
|
10662
10827
|
init_task_contract();
|
|
10663
10828
|
init_task_evaluation();
|
|
10664
10829
|
init_methods();
|
|
@@ -10668,7 +10833,9 @@ var init_task_context = __esm(() => {
|
|
|
10668
10833
|
});
|
|
10669
10834
|
|
|
10670
10835
|
// packages/workit-core/src/core/route-intent.ts
|
|
10671
|
-
var init_route_intent = () => {
|
|
10836
|
+
var init_route_intent = __esm(() => {
|
|
10837
|
+
init_task_store();
|
|
10838
|
+
});
|
|
10672
10839
|
|
|
10673
10840
|
// packages/workit-core/src/core/external-action.ts
|
|
10674
10841
|
var externalActionSchema;
|
|
@@ -10687,7 +10854,11 @@ var init_external_action = __esm(() => {
|
|
|
10687
10854
|
}).strict(),
|
|
10688
10855
|
object({
|
|
10689
10856
|
operation: literal("git.commit"),
|
|
10690
|
-
payload: object({
|
|
10857
|
+
payload: object({
|
|
10858
|
+
message: string2().min(1).optional(),
|
|
10859
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
10860
|
+
plan_branch: string2().min(1).optional()
|
|
10861
|
+
}).strict()
|
|
10691
10862
|
}).strict(),
|
|
10692
10863
|
object({
|
|
10693
10864
|
operation: literal("git.push"),
|
|
@@ -10819,7 +10990,7 @@ function resolveRequirements(input) {
|
|
|
10819
10990
|
].join(", ")}`,
|
|
10820
10991
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
10821
10992
|
before: "dependent_action",
|
|
10822
|
-
dependentAction:
|
|
10993
|
+
dependentAction: null,
|
|
10823
10994
|
acceptanceAllowed: false
|
|
10824
10995
|
}));
|
|
10825
10996
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -10830,7 +11001,7 @@ function resolveRequirements(input) {
|
|
|
10830
11001
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
10831
11002
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
10832
11003
|
before: "dependent_action",
|
|
10833
|
-
dependentAction:
|
|
11004
|
+
dependentAction: null,
|
|
10834
11005
|
acceptanceAllowed: false
|
|
10835
11006
|
}));
|
|
10836
11007
|
if (signals.behaviorChange.value === true)
|
|
@@ -11082,58 +11253,6 @@ var init_policy_resolver = __esm(() => {
|
|
|
11082
11253
|
}).strict();
|
|
11083
11254
|
});
|
|
11084
11255
|
|
|
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
11256
|
// packages/workit-core/src/core/task-engine.ts
|
|
11138
11257
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
11139
11258
|
|
|
@@ -11195,7 +11314,7 @@ class WorkitCore {
|
|
|
11195
11314
|
activeWorkerBlocker(task, workspace) {
|
|
11196
11315
|
if (workspace.writer)
|
|
11197
11316
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11198
|
-
if (task.workers.some((entry) =>
|
|
11317
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
11199
11318
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11200
11319
|
return null;
|
|
11201
11320
|
}
|
|
@@ -11432,11 +11551,6 @@ class WorkitCore {
|
|
|
11432
11551
|
if (helper && !helper.ok)
|
|
11433
11552
|
return helper;
|
|
11434
11553
|
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
11554
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
11441
11555
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
11442
11556
|
if (evidence.kind === "review") {
|
|
@@ -11453,6 +11567,11 @@ class WorkitCore {
|
|
|
11453
11567
|
evidence.beforeCandidateId ??= null;
|
|
11454
11568
|
evidence.candidateId ??= null;
|
|
11455
11569
|
}
|
|
11570
|
+
if (helper?.ok) {
|
|
11571
|
+
const assignment = helper.data.data.assignment;
|
|
11572
|
+
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))
|
|
11573
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11574
|
+
}
|
|
11456
11575
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11457
11576
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
11458
11577
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -11506,7 +11625,12 @@ class WorkitCore {
|
|
|
11506
11625
|
const input = parsed.data;
|
|
11507
11626
|
if (nativeRequired && input.action !== "record")
|
|
11508
11627
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
11509
|
-
|
|
11628
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
11629
|
+
if (stated && input.purpose === "action")
|
|
11630
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
11631
|
+
if (stated && !input.binding?.statedChoice)
|
|
11632
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
11633
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
11510
11634
|
return failure("permission_denied", "native decision observation is required");
|
|
11511
11635
|
const task = this.store.readTask(input.taskId);
|
|
11512
11636
|
if (!task.ok)
|
|
@@ -11539,7 +11663,7 @@ class WorkitCore {
|
|
|
11539
11663
|
consumption: null
|
|
11540
11664
|
};
|
|
11541
11665
|
const data = { ...base, digest: decisionDigest(base) };
|
|
11542
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
11666
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
11543
11667
|
observation: nativeObservation,
|
|
11544
11668
|
expected: {
|
|
11545
11669
|
taskId: task.data.id,
|
|
@@ -12348,6 +12472,9 @@ class WorkitCore {
|
|
|
12348
12472
|
id: task.id,
|
|
12349
12473
|
revision: task.revision,
|
|
12350
12474
|
workspaceRevision: workspace.data.revision,
|
|
12475
|
+
createdAt: task.createdAt,
|
|
12476
|
+
updatedAt: task.updatedAt,
|
|
12477
|
+
runtime: task.runtime ?? null,
|
|
12351
12478
|
objective: task.intent.data.objective,
|
|
12352
12479
|
status: task.status,
|
|
12353
12480
|
closure: task.closure,
|
|
@@ -12433,7 +12560,7 @@ class WorkitCore {
|
|
|
12433
12560
|
const base = reconcileResume(effectiveView);
|
|
12434
12561
|
if (!base.ok)
|
|
12435
12562
|
return base;
|
|
12436
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
12563
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
12437
12564
|
const blockers = [...base.data.blockers];
|
|
12438
12565
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
12439
12566
|
blockers.push({
|
|
@@ -12500,7 +12627,8 @@ class WorkitCore {
|
|
|
12500
12627
|
...current,
|
|
12501
12628
|
status,
|
|
12502
12629
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
12503
|
-
progress:
|
|
12630
|
+
progress: current.progress,
|
|
12631
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
12504
12632
|
})
|
|
12505
12633
|
});
|
|
12506
12634
|
if (!changed.ok)
|
|
@@ -12536,7 +12664,7 @@ class WorkitCore {
|
|
|
12536
12664
|
return workspace;
|
|
12537
12665
|
if (!workspace.data)
|
|
12538
12666
|
return failure("not_found", "workspace not found");
|
|
12539
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
12667
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12540
12668
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
12541
12669
|
this.fillRevisions(input, task.data, workspace.data);
|
|
12542
12670
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -12736,15 +12864,20 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
|
|
|
12736
12864
|
return failure("invalid_transition", "worker has not started");
|
|
12737
12865
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
12738
12866
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
12867
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
12739
12868
|
const nextEntry = {
|
|
12740
12869
|
...entry,
|
|
12741
12870
|
recordedAt: input.now ?? entry.recordedAt,
|
|
12742
12871
|
provenance: authority.provenance,
|
|
12743
|
-
data: {
|
|
12872
|
+
data: {
|
|
12873
|
+
...entry.data,
|
|
12874
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
12875
|
+
session: input.session
|
|
12876
|
+
}
|
|
12744
12877
|
};
|
|
12745
12878
|
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
12879
|
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 ===
|
|
12880
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
12748
12881
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
12749
12882
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
12750
12883
|
taskId: task.data.id,
|
|
@@ -13072,8 +13205,8 @@ var init_core3 = __esm(() => {
|
|
|
13072
13205
|
});
|
|
13073
13206
|
|
|
13074
13207
|
// packages/workit-cursor/hooks/workit-hook.ts
|
|
13075
|
-
import { existsSync as
|
|
13076
|
-
import
|
|
13208
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
13209
|
+
import path7 from "node:path";
|
|
13077
13210
|
var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapabilities = (availability = {}) => {
|
|
13078
13211
|
const has = (name) => availability[name] === true;
|
|
13079
13212
|
return [
|
|
@@ -13105,6 +13238,13 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13105
13238
|
reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
|
|
13106
13239
|
refs: [hostRef("subagentStart")]
|
|
13107
13240
|
},
|
|
13241
|
+
{
|
|
13242
|
+
name: "fresh-context-review",
|
|
13243
|
+
surface: "subagentStart",
|
|
13244
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
13245
|
+
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",
|
|
13246
|
+
refs: [hostRef("subagentStart")]
|
|
13247
|
+
},
|
|
13108
13248
|
{
|
|
13109
13249
|
name: "arbitrary_shell_write",
|
|
13110
13250
|
surface: "unobservable_shell",
|
|
@@ -13127,7 +13267,7 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13127
13267
|
if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
|
|
13128
13268
|
return { ok: false, error: "exactly one workspace root is required" };
|
|
13129
13269
|
const root = roots[0];
|
|
13130
|
-
if (!
|
|
13270
|
+
if (!path7.isAbsolute(root) || !existsSync4(root))
|
|
13131
13271
|
return { ok: false, error: "workspace root must be an existing absolute path" };
|
|
13132
13272
|
const event = value.hook_event_name;
|
|
13133
13273
|
const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
|