@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
package/dist/mcp-server.js
CHANGED
|
@@ -12479,8 +12479,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
12479
12479
|
|
|
12480
12480
|
// packages/workit-core/src/core.ts
|
|
12481
12481
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12482
|
-
import { existsSync as
|
|
12483
|
-
import
|
|
12482
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
12483
|
+
import path7 from "node:path";
|
|
12484
12484
|
|
|
12485
12485
|
// packages/workit-core/src/core/task-contract.ts
|
|
12486
12486
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -20272,6 +20272,7 @@ var decisionSchema = object4({
|
|
|
20272
20272
|
scope: scopeSchema,
|
|
20273
20273
|
presented: text,
|
|
20274
20274
|
approvedContent: text,
|
|
20275
|
+
displayed: text.optional(),
|
|
20275
20276
|
contentRefs: array3(refSchema)
|
|
20276
20277
|
}).strict(),
|
|
20277
20278
|
digest,
|
|
@@ -20350,6 +20351,10 @@ var actionProgressListSchema = array3(actionProgressSchema).check((ctx) => {
|
|
|
20350
20351
|
path: ["decisionId"]
|
|
20351
20352
|
});
|
|
20352
20353
|
});
|
|
20354
|
+
var runtimeSchema = object4({
|
|
20355
|
+
createdWith: text.nullable(),
|
|
20356
|
+
updatedWith: text
|
|
20357
|
+
}).strict();
|
|
20353
20358
|
var taskRecordSchema = object4({
|
|
20354
20359
|
schemaVersion: literal3(1),
|
|
20355
20360
|
id,
|
|
@@ -20363,6 +20368,8 @@ var taskRecordSchema = object4({
|
|
|
20363
20368
|
status: _enum2(["active", "paused", "closed"]),
|
|
20364
20369
|
closure: closureSchema.nullable(),
|
|
20365
20370
|
progress: progressSchema,
|
|
20371
|
+
pauseReason: text.nullable().optional(),
|
|
20372
|
+
runtime: runtimeSchema.optional(),
|
|
20366
20373
|
assessments: array3(entrySchema(assessmentSchema)),
|
|
20367
20374
|
policy: policySchema.nullable(),
|
|
20368
20375
|
policyChanges: array3(policyChangeSchema),
|
|
@@ -20378,6 +20385,7 @@ var workspaceRecordSchema = object4({
|
|
|
20378
20385
|
id,
|
|
20379
20386
|
revision,
|
|
20380
20387
|
root: nonEmpty,
|
|
20388
|
+
runtime: runtimeSchema.optional(),
|
|
20381
20389
|
writer: object4({ state: _enum2(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
20382
20390
|
}).strict();
|
|
20383
20391
|
var capabilitySchema = object4({
|
|
@@ -20405,6 +20413,9 @@ var taskSummarySchema = object4({
|
|
|
20405
20413
|
id,
|
|
20406
20414
|
revision,
|
|
20407
20415
|
workspaceRevision: revision,
|
|
20416
|
+
createdAt: utc,
|
|
20417
|
+
updatedAt: utc,
|
|
20418
|
+
runtime: runtimeSchema.nullable(),
|
|
20408
20419
|
objective: text,
|
|
20409
20420
|
status: _enum2(["active", "paused", "closed"]),
|
|
20410
20421
|
closure: closureSchema.nullable(),
|
|
@@ -20475,7 +20486,7 @@ var taskOperations = {
|
|
|
20475
20486
|
expectedWorkspaceRevision: revision.optional(),
|
|
20476
20487
|
outcome: outcomeSchema,
|
|
20477
20488
|
summary: text,
|
|
20478
|
-
decisionIds: array3(id)
|
|
20489
|
+
decisionIds: array3(id).optional()
|
|
20479
20490
|
})
|
|
20480
20491
|
};
|
|
20481
20492
|
var policyOperations = {
|
|
@@ -20567,14 +20578,15 @@ var writerOperations = {
|
|
|
20567
20578
|
...taskId,
|
|
20568
20579
|
expectedRevision: revision.optional(),
|
|
20569
20580
|
expectedWorkspaceRevision: revision.optional(),
|
|
20570
|
-
workerId: nullableId.optional()
|
|
20581
|
+
workerId: nullableId.optional(),
|
|
20582
|
+
reason: text.optional()
|
|
20571
20583
|
}),
|
|
20572
20584
|
release: operation({
|
|
20573
20585
|
action: literal3("release"),
|
|
20574
20586
|
...taskId,
|
|
20575
20587
|
expectedRevision: revision.optional(),
|
|
20576
20588
|
expectedWorkspaceRevision: revision.optional(),
|
|
20577
|
-
reason: text
|
|
20589
|
+
reason: text.optional()
|
|
20578
20590
|
})
|
|
20579
20591
|
};
|
|
20580
20592
|
var stateOperations = {
|
|
@@ -20781,7 +20793,26 @@ function requirementId(input) {
|
|
|
20781
20793
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
20782
20794
|
import * as fs4 from "node:fs";
|
|
20783
20795
|
import { hostname as hostname2 } from "node:os";
|
|
20784
|
-
import
|
|
20796
|
+
import path4 from "node:path";
|
|
20797
|
+
|
|
20798
|
+
// packages/workit-core/src/core/package-root.ts
|
|
20799
|
+
import { existsSync } from "node:fs";
|
|
20800
|
+
import path from "node:path";
|
|
20801
|
+
import { fileURLToPath } from "node:url";
|
|
20802
|
+
var packageRoot = () => {
|
|
20803
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
20804
|
+
while (true) {
|
|
20805
|
+
const parent = path.dirname(dir);
|
|
20806
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
20807
|
+
return dir;
|
|
20808
|
+
dir = parent;
|
|
20809
|
+
}
|
|
20810
|
+
};
|
|
20811
|
+
var assetRoot = () => {
|
|
20812
|
+
const root = packageRoot();
|
|
20813
|
+
const assets = path.join(root, "assets");
|
|
20814
|
+
return existsSync(assets) ? assets : root;
|
|
20815
|
+
};
|
|
20785
20816
|
|
|
20786
20817
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
20787
20818
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -20832,7 +20863,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
20832
20863
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
20833
20864
|
import { randomBytes } from "node:crypto";
|
|
20834
20865
|
import fsSync from "node:fs";
|
|
20835
|
-
import
|
|
20866
|
+
import path2 from "node:path";
|
|
20836
20867
|
|
|
20837
20868
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
20838
20869
|
import fs from "node:fs";
|
|
@@ -20922,13 +20953,13 @@ ${ownershipToken}
|
|
|
20922
20953
|
return { raw, ownershipToken };
|
|
20923
20954
|
}
|
|
20924
20955
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
20925
|
-
const resolved =
|
|
20926
|
-
const lexicalRelative =
|
|
20927
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
20928
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
20956
|
+
const resolved = path2.resolve(lockPath);
|
|
20957
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
20958
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
20959
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
20929
20960
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
20930
20961
|
}
|
|
20931
|
-
return relative.split(
|
|
20962
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
20932
20963
|
}
|
|
20933
20964
|
function parseSidecarLockPayload(raw, parser) {
|
|
20934
20965
|
if (parser) {
|
|
@@ -21075,7 +21106,7 @@ function getFsSafeLockConfig() {
|
|
|
21075
21106
|
|
|
21076
21107
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
21077
21108
|
import fs3 from "node:fs";
|
|
21078
|
-
import
|
|
21109
|
+
import path3 from "node:path";
|
|
21079
21110
|
|
|
21080
21111
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
21081
21112
|
function sleepSync(ms) {
|
|
@@ -21166,26 +21197,26 @@ function canonicalLockParentSync(parent) {
|
|
|
21166
21197
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
21167
21198
|
}
|
|
21168
21199
|
function normalizeTargetPath(targetPath) {
|
|
21169
|
-
const resolved =
|
|
21170
|
-
fs3.mkdirSync(
|
|
21200
|
+
const resolved = path3.resolve(targetPath);
|
|
21201
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
21171
21202
|
try {
|
|
21172
|
-
return
|
|
21203
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
21173
21204
|
} catch {
|
|
21174
21205
|
return resolved;
|
|
21175
21206
|
}
|
|
21176
21207
|
}
|
|
21177
21208
|
function boundedLockPath(lockPath, lockRoot) {
|
|
21178
|
-
const resolved =
|
|
21209
|
+
const resolved = path3.resolve(lockPath);
|
|
21179
21210
|
if (!lockRoot)
|
|
21180
21211
|
return resolved;
|
|
21181
21212
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
21182
|
-
const parent =
|
|
21213
|
+
const parent = path3.dirname(resolved);
|
|
21183
21214
|
const parentReal = canonicalLockParentSync(parent);
|
|
21184
|
-
const parentRelative =
|
|
21185
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
21215
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
21216
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
21186
21217
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
21187
21218
|
}
|
|
21188
|
-
return
|
|
21219
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
21189
21220
|
}
|
|
21190
21221
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
21191
21222
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -21398,6 +21429,33 @@ var metadataLockSchema = object4({
|
|
|
21398
21429
|
nonce: string5().min(1)
|
|
21399
21430
|
}).strict();
|
|
21400
21431
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21432
|
+
var cachedRuntimeVersion = null;
|
|
21433
|
+
var runtimeVersion = () => {
|
|
21434
|
+
if (cachedRuntimeVersion === null) {
|
|
21435
|
+
try {
|
|
21436
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
21437
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
21438
|
+
} catch {
|
|
21439
|
+
cachedRuntimeVersion = "0.0.0";
|
|
21440
|
+
}
|
|
21441
|
+
}
|
|
21442
|
+
return cachedRuntimeVersion;
|
|
21443
|
+
};
|
|
21444
|
+
var stampNew = (record) => ({
|
|
21445
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
21446
|
+
updatedWith: runtimeVersion()
|
|
21447
|
+
});
|
|
21448
|
+
var isNewerVersion = (candidate, current) => {
|
|
21449
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
21450
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
21451
|
+
for (let index = 0;index < 3; index += 1) {
|
|
21452
|
+
const left = next[index] ?? 0;
|
|
21453
|
+
const right = now[index] ?? 0;
|
|
21454
|
+
if (left !== right)
|
|
21455
|
+
return left > right;
|
|
21456
|
+
}
|
|
21457
|
+
return false;
|
|
21458
|
+
};
|
|
21401
21459
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
21402
21460
|
`;
|
|
21403
21461
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -21424,12 +21482,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
21424
21482
|
class TaskStore {
|
|
21425
21483
|
root;
|
|
21426
21484
|
constructor(root) {
|
|
21427
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
21485
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
21428
21486
|
}
|
|
21429
21487
|
readTask(taskId) {
|
|
21430
21488
|
if (!validId(taskId))
|
|
21431
21489
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
21432
|
-
const result = this.readRecord(
|
|
21490
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
21433
21491
|
if (!result.exists)
|
|
21434
21492
|
return failure("not_found", "task not found", { taskId });
|
|
21435
21493
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -21463,7 +21521,7 @@ class TaskStore {
|
|
|
21463
21521
|
});
|
|
21464
21522
|
const tasks = [];
|
|
21465
21523
|
for (const name of names.sort()) {
|
|
21466
|
-
const item = this.readRecord(
|
|
21524
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
21467
21525
|
if (!item.exists)
|
|
21468
21526
|
continue;
|
|
21469
21527
|
if (!item.result.ok)
|
|
@@ -21497,7 +21555,7 @@ class TaskStore {
|
|
|
21497
21555
|
if (!current.ok)
|
|
21498
21556
|
return current;
|
|
21499
21557
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
21500
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21558
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21501
21559
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
21502
21560
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21503
21561
|
});
|
|
@@ -21509,11 +21567,17 @@ class TaskStore {
|
|
|
21509
21567
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
21510
21568
|
if (current.data && !previousWorkspaceBytes)
|
|
21511
21569
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
21512
|
-
const workspace = current.data ? {
|
|
21570
|
+
const workspace = current.data ? {
|
|
21571
|
+
...current.data,
|
|
21572
|
+
revision: newRevision(),
|
|
21573
|
+
root: this.root,
|
|
21574
|
+
runtime: stampNew(current.data)
|
|
21575
|
+
} : {
|
|
21513
21576
|
schemaVersion: SCHEMA_VERSION,
|
|
21514
21577
|
id: newId(),
|
|
21515
21578
|
revision: newRevision(),
|
|
21516
21579
|
root: this.root,
|
|
21580
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21517
21581
|
writer: null
|
|
21518
21582
|
};
|
|
21519
21583
|
const timestamp = value.now ?? now();
|
|
@@ -21535,6 +21599,7 @@ class TaskStore {
|
|
|
21535
21599
|
status: "active",
|
|
21536
21600
|
closure: null,
|
|
21537
21601
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
21602
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21538
21603
|
assessments: [],
|
|
21539
21604
|
policy: null,
|
|
21540
21605
|
policyChanges: [],
|
|
@@ -21565,7 +21630,7 @@ class TaskStore {
|
|
|
21565
21630
|
if (!current.ok)
|
|
21566
21631
|
return current;
|
|
21567
21632
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
21568
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21633
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21569
21634
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
21570
21635
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21571
21636
|
});
|
|
@@ -21573,11 +21638,17 @@ class TaskStore {
|
|
|
21573
21638
|
if (current.data && !previousWorkspaceBytes)
|
|
21574
21639
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
21575
21640
|
const timestamp = input.now ?? now();
|
|
21576
|
-
const workspace = current.data ? {
|
|
21641
|
+
const workspace = current.data ? {
|
|
21642
|
+
...current.data,
|
|
21643
|
+
revision: newRevision(),
|
|
21644
|
+
root: this.root,
|
|
21645
|
+
runtime: stampNew(current.data)
|
|
21646
|
+
} : {
|
|
21577
21647
|
schemaVersion: SCHEMA_VERSION,
|
|
21578
21648
|
id: input.workspaceId ?? newId(),
|
|
21579
21649
|
revision: newRevision(),
|
|
21580
21650
|
root: this.root,
|
|
21651
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21581
21652
|
writer: null
|
|
21582
21653
|
};
|
|
21583
21654
|
const task = {
|
|
@@ -21585,7 +21656,11 @@ class TaskStore {
|
|
|
21585
21656
|
workspaceId: workspace.id,
|
|
21586
21657
|
revision: newRevision(),
|
|
21587
21658
|
createdAt: timestamp,
|
|
21588
|
-
updatedAt: timestamp
|
|
21659
|
+
updatedAt: timestamp,
|
|
21660
|
+
runtime: {
|
|
21661
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
21662
|
+
updatedWith: runtimeVersion()
|
|
21663
|
+
}
|
|
21589
21664
|
};
|
|
21590
21665
|
const validTask = taskRecordSchema.safeParse(task);
|
|
21591
21666
|
if (!validTask.success)
|
|
@@ -21634,7 +21709,8 @@ class TaskStore {
|
|
|
21634
21709
|
workspaceId: current.data.workspaceId,
|
|
21635
21710
|
createdAt: current.data.createdAt,
|
|
21636
21711
|
revision: context.revision,
|
|
21637
|
-
updatedAt: context.now
|
|
21712
|
+
updatedAt: context.now,
|
|
21713
|
+
runtime: stampNew(current.data)
|
|
21638
21714
|
};
|
|
21639
21715
|
const valid = taskRecordSchema.safeParse(record);
|
|
21640
21716
|
if (!valid.success)
|
|
@@ -21668,7 +21744,8 @@ class TaskStore {
|
|
|
21668
21744
|
...changed.data,
|
|
21669
21745
|
id: current.data.id,
|
|
21670
21746
|
root: this.root,
|
|
21671
|
-
revision: context.revision
|
|
21747
|
+
revision: context.revision,
|
|
21748
|
+
runtime: stampNew(current.data)
|
|
21672
21749
|
};
|
|
21673
21750
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
21674
21751
|
if (!valid.success)
|
|
@@ -21711,7 +21788,8 @@ class TaskStore {
|
|
|
21711
21788
|
...changedWorkspace.data,
|
|
21712
21789
|
id: workspace.data.id,
|
|
21713
21790
|
root: this.root,
|
|
21714
|
-
revision: workspaceContext.revision
|
|
21791
|
+
revision: workspaceContext.revision,
|
|
21792
|
+
runtime: stampNew(workspace.data)
|
|
21715
21793
|
});
|
|
21716
21794
|
if (!nextWorkspace.success)
|
|
21717
21795
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -21736,7 +21814,8 @@ class TaskStore {
|
|
|
21736
21814
|
workspaceId: task.data.workspaceId,
|
|
21737
21815
|
createdAt: task.data.createdAt,
|
|
21738
21816
|
revision: taskContext.revision,
|
|
21739
|
-
updatedAt: taskContext.now
|
|
21817
|
+
updatedAt: taskContext.now,
|
|
21818
|
+
runtime: stampNew(task.data)
|
|
21740
21819
|
});
|
|
21741
21820
|
if (!nextTask.success) {
|
|
21742
21821
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -21763,7 +21842,7 @@ class TaskStore {
|
|
|
21763
21842
|
if (match)
|
|
21764
21843
|
candidates.push({
|
|
21765
21844
|
target: match[1],
|
|
21766
|
-
path:
|
|
21845
|
+
path: path4.join(this.recoveryDir, name),
|
|
21767
21846
|
digest: match[3]
|
|
21768
21847
|
});
|
|
21769
21848
|
}
|
|
@@ -21956,7 +22035,8 @@ class TaskStore {
|
|
|
21956
22035
|
const value = {
|
|
21957
22036
|
...workspace,
|
|
21958
22037
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
21959
|
-
revision: newRevision()
|
|
22038
|
+
revision: newRevision(),
|
|
22039
|
+
runtime: stampNew(workspace)
|
|
21960
22040
|
};
|
|
21961
22041
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
21962
22042
|
}
|
|
@@ -22034,7 +22114,7 @@ class TaskStore {
|
|
|
22034
22114
|
}
|
|
22035
22115
|
fs4.renameSync(temporary, file);
|
|
22036
22116
|
temporary = undefined;
|
|
22037
|
-
this.fsyncDirectory(
|
|
22117
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
22038
22118
|
return success(null, null, value);
|
|
22039
22119
|
} catch (error) {
|
|
22040
22120
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -22048,9 +22128,9 @@ class TaskStore {
|
|
|
22048
22128
|
}
|
|
22049
22129
|
}
|
|
22050
22130
|
saveRecovery(file, bytes) {
|
|
22051
|
-
const target =
|
|
22052
|
-
const id = target === "task" ?
|
|
22053
|
-
const destination =
|
|
22131
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
22132
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
22133
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
22054
22134
|
if (fs4.existsSync(destination)) {
|
|
22055
22135
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
22056
22136
|
return;
|
|
@@ -22128,7 +22208,7 @@ class TaskStore {
|
|
|
22128
22208
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
22129
22209
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
22130
22210
|
return null;
|
|
22131
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
22211
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
22132
22212
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
22133
22213
|
return null;
|
|
22134
22214
|
ids.add(parsed.data.workspaceId);
|
|
@@ -22170,7 +22250,12 @@ class TaskStore {
|
|
|
22170
22250
|
if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
22171
22251
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
22172
22252
|
const parsed = schema.safeParse(value);
|
|
22173
|
-
|
|
22253
|
+
if (parsed.success)
|
|
22254
|
+
return success(null, null, parsed.data);
|
|
22255
|
+
const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
|
|
22256
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
22257
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
22258
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
22174
22259
|
}
|
|
22175
22260
|
processStart(pid) {
|
|
22176
22261
|
try {
|
|
@@ -22180,43 +22265,98 @@ class TaskStore {
|
|
|
22180
22265
|
}
|
|
22181
22266
|
}
|
|
22182
22267
|
conflict(expected, actual) {
|
|
22183
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
22268
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
22184
22269
|
expectedRevision: expected,
|
|
22185
22270
|
actualRevision: actual
|
|
22186
22271
|
});
|
|
22187
22272
|
}
|
|
22188
22273
|
taskPath(taskId) {
|
|
22189
|
-
return
|
|
22274
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
22190
22275
|
}
|
|
22191
22276
|
get workitDir() {
|
|
22192
|
-
return
|
|
22277
|
+
return path4.join(this.root, ".workit");
|
|
22193
22278
|
}
|
|
22194
22279
|
get tasksDir() {
|
|
22195
|
-
return
|
|
22280
|
+
return path4.join(this.workitDir, "tasks");
|
|
22196
22281
|
}
|
|
22197
22282
|
get recoveryDir() {
|
|
22198
|
-
return
|
|
22283
|
+
return path4.join(this.workitDir, "recovery");
|
|
22199
22284
|
}
|
|
22200
22285
|
get workspacePath() {
|
|
22201
|
-
return
|
|
22286
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
22202
22287
|
}
|
|
22203
22288
|
get lockPath() {
|
|
22204
|
-
return
|
|
22289
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
22205
22290
|
}
|
|
22206
22291
|
get gitignorePath() {
|
|
22207
|
-
return
|
|
22292
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
22208
22293
|
}
|
|
22209
22294
|
}
|
|
22295
|
+
// packages/workit-core/src/core/workers.ts
|
|
22296
|
+
var same = (left, right) => {
|
|
22297
|
+
try {
|
|
22298
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
22299
|
+
} catch {
|
|
22300
|
+
return false;
|
|
22301
|
+
}
|
|
22302
|
+
};
|
|
22303
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
22304
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
22305
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
22306
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
22307
|
+
const code = char.charCodeAt(0);
|
|
22308
|
+
return code < 32 || code === 127;
|
|
22309
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
22310
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
22311
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
22312
|
+
function assertProductWriteAllowed(input) {
|
|
22313
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
22314
|
+
return failure("invalid_input", "invalid product write path");
|
|
22315
|
+
if (!input.store)
|
|
22316
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
22317
|
+
const task = input.store.readTask(input.task.id);
|
|
22318
|
+
if (!task.ok)
|
|
22319
|
+
return task;
|
|
22320
|
+
const workspace = input.store.readWorkspace();
|
|
22321
|
+
if (!workspace.ok)
|
|
22322
|
+
return workspace;
|
|
22323
|
+
if (!workspace.data)
|
|
22324
|
+
return failure("not_found", "workspace not found");
|
|
22325
|
+
const workspaceRecord = workspace.data;
|
|
22326
|
+
if (task.data.status !== "active")
|
|
22327
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
22328
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
22329
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
22330
|
+
const writer = workspaceRecord.writer;
|
|
22331
|
+
if (!writer)
|
|
22332
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
22333
|
+
if (writer.state === "uncertain")
|
|
22334
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
22335
|
+
owner: writer.owner
|
|
22336
|
+
});
|
|
22337
|
+
const caller = callerValue(input.caller);
|
|
22338
|
+
const workerId = workerIdOf(input.caller);
|
|
22339
|
+
const owner = writer.owner;
|
|
22340
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
22341
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
22342
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
22343
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
22344
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
22345
|
+
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))))
|
|
22346
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
22347
|
+
return success(null, workspaceRecord.revision, owner);
|
|
22348
|
+
}
|
|
22349
|
+
|
|
22210
22350
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22211
22351
|
import { createHash as createHash4 } from "node:crypto";
|
|
22212
22352
|
import * as fs6 from "node:fs";
|
|
22213
22353
|
import { spawnSync } from "node:child_process";
|
|
22214
|
-
import
|
|
22354
|
+
import path6 from "node:path";
|
|
22215
22355
|
|
|
22216
22356
|
// packages/workit-core/src/core/authority.ts
|
|
22217
22357
|
import { createHash as createHash3 } from "node:crypto";
|
|
22218
22358
|
import * as fs5 from "node:fs";
|
|
22219
|
-
import
|
|
22359
|
+
import path5 from "node:path";
|
|
22220
22360
|
var verifiedAuthorities = new WeakMap;
|
|
22221
22361
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
22222
22362
|
const token = {};
|
|
@@ -22343,20 +22483,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
22343
22483
|
continue;
|
|
22344
22484
|
if (!reference.digest)
|
|
22345
22485
|
return failure("invalid_input", "document references require a byte digest");
|
|
22346
|
-
const target =
|
|
22347
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
22348
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
22486
|
+
const target = path5.resolve(root, reference.path);
|
|
22487
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
22488
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22489
|
+
fields: [
|
|
22490
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
22491
|
+
]
|
|
22492
|
+
});
|
|
22349
22493
|
try {
|
|
22350
|
-
const relative =
|
|
22494
|
+
const relative = path5.relative(root, target);
|
|
22351
22495
|
let current = root;
|
|
22352
|
-
for (const segment of relative.split(
|
|
22496
|
+
for (const segment of relative.split(path5.sep)) {
|
|
22353
22497
|
if (!segment)
|
|
22354
22498
|
continue;
|
|
22355
|
-
current =
|
|
22499
|
+
current = path5.join(current, segment);
|
|
22356
22500
|
const stat = fs5.lstatSync(current);
|
|
22357
22501
|
const real = fs5.realpathSync(current);
|
|
22358
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
22359
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
22502
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
22503
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22504
|
+
fields: [
|
|
22505
|
+
{
|
|
22506
|
+
path: "contentRefs.path",
|
|
22507
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
22508
|
+
}
|
|
22509
|
+
]
|
|
22510
|
+
});
|
|
22360
22511
|
if (stat.isSymbolicLink())
|
|
22361
22512
|
return failure("invalid_input", "document reference uses a symlink");
|
|
22362
22513
|
if (current === target && !stat.isFile())
|
|
@@ -22703,14 +22854,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
22703
22854
|
|
|
22704
22855
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22705
22856
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
22706
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
22707
|
-
var relative = (root, candidate) =>
|
|
22857
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
22858
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
22708
22859
|
var canonicalPath = (value) => {
|
|
22709
22860
|
if (typeof value !== "string" || !value)
|
|
22710
22861
|
return null;
|
|
22711
22862
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
22712
22863
|
return null;
|
|
22713
|
-
const normalized =
|
|
22864
|
+
const normalized = path6.posix.normalize(value);
|
|
22714
22865
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
22715
22866
|
return null;
|
|
22716
22867
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -22758,14 +22909,14 @@ var gitPaths = (root) => {
|
|
|
22758
22909
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
22759
22910
|
};
|
|
22760
22911
|
}
|
|
22761
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22912
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
22762
22913
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
22763
22914
|
if (stagedDeleted.status !== 0) {
|
|
22764
22915
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
22765
22916
|
if (!/not a git repository/i.test(stderr))
|
|
22766
22917
|
return { paths, uncertain: true, git: true };
|
|
22767
22918
|
} else if (stagedDeleted.stdout) {
|
|
22768
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22919
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
22769
22920
|
}
|
|
22770
22921
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
22771
22922
|
};
|
|
@@ -22780,7 +22931,7 @@ var walk = (root, directory, output) => {
|
|
|
22780
22931
|
for (const name of names) {
|
|
22781
22932
|
if (name === ".git" || name === ".workit")
|
|
22782
22933
|
continue;
|
|
22783
|
-
const target =
|
|
22934
|
+
const target = path6.join(directory, name);
|
|
22784
22935
|
let stat;
|
|
22785
22936
|
try {
|
|
22786
22937
|
stat = fs6.lstatSync(target);
|
|
@@ -22798,17 +22949,17 @@ var walk = (root, directory, output) => {
|
|
|
22798
22949
|
var scopeRoots = (root, scope) => {
|
|
22799
22950
|
const roots = [];
|
|
22800
22951
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
22801
|
-
const target =
|
|
22952
|
+
const target = path6.resolve(root, item);
|
|
22802
22953
|
if (!inside(root, target))
|
|
22803
|
-
return failure("invalid_input",
|
|
22954
|
+
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` }] });
|
|
22804
22955
|
let ancestor = target;
|
|
22805
22956
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
22806
|
-
ancestor =
|
|
22957
|
+
ancestor = path6.dirname(ancestor);
|
|
22807
22958
|
try {
|
|
22808
22959
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
22809
|
-
return failure("invalid_input",
|
|
22960
|
+
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` }] });
|
|
22810
22961
|
} catch {
|
|
22811
|
-
return failure("invalid_input",
|
|
22962
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
22812
22963
|
}
|
|
22813
22964
|
roots.push(target);
|
|
22814
22965
|
}
|
|
@@ -22823,7 +22974,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22823
22974
|
}
|
|
22824
22975
|
const normalizedScope = canonicalScope(scope);
|
|
22825
22976
|
if (!normalizedScope)
|
|
22826
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
22977
|
+
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" }] });
|
|
22827
22978
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
22828
22979
|
if (!roots.ok)
|
|
22829
22980
|
return roots;
|
|
@@ -22855,7 +23006,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22855
23006
|
for (const item of [...names].sort()) {
|
|
22856
23007
|
if (!scopeMatches(item, normalizedScope))
|
|
22857
23008
|
continue;
|
|
22858
|
-
const target =
|
|
23009
|
+
const target = path6.join(checkout, item);
|
|
22859
23010
|
try {
|
|
22860
23011
|
const stat = fs6.lstatSync(target);
|
|
22861
23012
|
if (stat.isSymbolicLink()) {
|
|
@@ -23012,7 +23163,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
23012
23163
|
return true;
|
|
23013
23164
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
23014
23165
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
23015
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
23166
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
23016
23167
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
23017
23168
|
});
|
|
23018
23169
|
if (passed.length) {
|
|
@@ -23108,7 +23259,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
23108
23259
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
23109
23260
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
23110
23261
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
23111
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
23262
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
23263
|
+
requirements: blocking.map((item) => {
|
|
23264
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
23265
|
+
return {
|
|
23266
|
+
requirementId: item.requirementId,
|
|
23267
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
23268
|
+
reason: item.reason,
|
|
23269
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
23270
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
23271
|
+
};
|
|
23272
|
+
})
|
|
23112
23273
|
});
|
|
23113
23274
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
23114
23275
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -23144,7 +23305,7 @@ function reconcileResume(view, observations = []) {
|
|
|
23144
23305
|
return candidate;
|
|
23145
23306
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
23146
23307
|
const blockers = [...view.task.progress.blockers];
|
|
23147
|
-
if (view.task.workers.some((entry) =>
|
|
23308
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
23148
23309
|
blockers.push({
|
|
23149
23310
|
reason: "worker state requires reconciliation",
|
|
23150
23311
|
dependentAction: "resume",
|
|
@@ -23177,7 +23338,11 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
23177
23338
|
}).strict(),
|
|
23178
23339
|
object4({
|
|
23179
23340
|
operation: literal3("git.commit"),
|
|
23180
|
-
payload: object4({
|
|
23341
|
+
payload: object4({
|
|
23342
|
+
message: string5().min(1).optional(),
|
|
23343
|
+
plan_steps: array3(string5().min(1)).optional(),
|
|
23344
|
+
plan_branch: string5().min(1).optional()
|
|
23345
|
+
}).strict()
|
|
23181
23346
|
}).strict(),
|
|
23182
23347
|
object4({
|
|
23183
23348
|
operation: literal3("git.push"),
|
|
@@ -23412,7 +23577,7 @@ function resolveRequirements(input) {
|
|
|
23412
23577
|
].join(", ")}`,
|
|
23413
23578
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
23414
23579
|
before: "dependent_action",
|
|
23415
|
-
dependentAction:
|
|
23580
|
+
dependentAction: null,
|
|
23416
23581
|
acceptanceAllowed: false
|
|
23417
23582
|
}));
|
|
23418
23583
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -23423,7 +23588,7 @@ function resolveRequirements(input) {
|
|
|
23423
23588
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
23424
23589
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
23425
23590
|
before: "dependent_action",
|
|
23426
|
-
dependentAction:
|
|
23591
|
+
dependentAction: null,
|
|
23427
23592
|
acceptanceAllowed: false
|
|
23428
23593
|
}));
|
|
23429
23594
|
if (signals.behaviorChange.value === true)
|
|
@@ -23581,59 +23746,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
23581
23746
|
return parsed.data;
|
|
23582
23747
|
}
|
|
23583
23748
|
|
|
23584
|
-
// packages/workit-core/src/core/workers.ts
|
|
23585
|
-
var same = (left, right) => {
|
|
23586
|
-
try {
|
|
23587
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
23588
|
-
} catch {
|
|
23589
|
-
return false;
|
|
23590
|
-
}
|
|
23591
|
-
};
|
|
23592
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
23593
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
23594
|
-
const code = char.charCodeAt(0);
|
|
23595
|
-
return code < 32 || code === 127;
|
|
23596
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
23597
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
23598
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
23599
|
-
function assertProductWriteAllowed(input) {
|
|
23600
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
23601
|
-
return failure("invalid_input", "invalid product write path");
|
|
23602
|
-
if (!input.store)
|
|
23603
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
23604
|
-
const task = input.store.readTask(input.task.id);
|
|
23605
|
-
if (!task.ok)
|
|
23606
|
-
return task;
|
|
23607
|
-
const workspace = input.store.readWorkspace();
|
|
23608
|
-
if (!workspace.ok)
|
|
23609
|
-
return workspace;
|
|
23610
|
-
if (!workspace.data)
|
|
23611
|
-
return failure("not_found", "workspace not found");
|
|
23612
|
-
const workspaceRecord = workspace.data;
|
|
23613
|
-
if (task.data.status !== "active")
|
|
23614
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
23615
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
23616
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
23617
|
-
const writer = workspaceRecord.writer;
|
|
23618
|
-
if (!writer)
|
|
23619
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
23620
|
-
if (writer.state === "uncertain")
|
|
23621
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
23622
|
-
owner: writer.owner
|
|
23623
|
-
});
|
|
23624
|
-
const caller = callerValue(input.caller);
|
|
23625
|
-
const workerId = workerIdOf(input.caller);
|
|
23626
|
-
const owner = writer.owner;
|
|
23627
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
23628
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
23629
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
23630
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
23631
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
23632
|
-
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))))
|
|
23633
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
23634
|
-
return success(null, workspaceRecord.revision, owner);
|
|
23635
|
-
}
|
|
23636
|
-
|
|
23637
23749
|
// packages/workit-core/src/core/task-engine.ts
|
|
23638
23750
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
23639
23751
|
kind,
|
|
@@ -23764,15 +23876,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
23764
23876
|
return failure("invalid_transition", "worker has not started");
|
|
23765
23877
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
23766
23878
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
23879
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
23767
23880
|
const nextEntry = {
|
|
23768
23881
|
...entry,
|
|
23769
23882
|
recordedAt: input.now ?? entry.recordedAt,
|
|
23770
23883
|
provenance: authority.provenance,
|
|
23771
|
-
data: {
|
|
23884
|
+
data: {
|
|
23885
|
+
...entry.data,
|
|
23886
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
23887
|
+
session: input.session
|
|
23888
|
+
}
|
|
23772
23889
|
};
|
|
23773
23890
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
23774
23891
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
23775
|
-
if (entry.data.state ===
|
|
23892
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
23776
23893
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
23777
23894
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
23778
23895
|
taskId: task.data.id,
|
|
@@ -24142,7 +24259,7 @@ class WorkitCore {
|
|
|
24142
24259
|
activeWorkerBlocker(task, workspace) {
|
|
24143
24260
|
if (workspace.writer)
|
|
24144
24261
|
return failure("recovery_required", "writer ownership must be released first");
|
|
24145
|
-
if (task.workers.some((entry) =>
|
|
24262
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
24146
24263
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
24147
24264
|
return null;
|
|
24148
24265
|
}
|
|
@@ -24379,11 +24496,6 @@ class WorkitCore {
|
|
|
24379
24496
|
if (helper && !helper.ok)
|
|
24380
24497
|
return helper;
|
|
24381
24498
|
const evidence = input.evidence;
|
|
24382
|
-
if (helper?.ok) {
|
|
24383
|
-
const assignment = helper.data.data.assignment;
|
|
24384
|
-
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))
|
|
24385
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
24386
|
-
}
|
|
24387
24499
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
24388
24500
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
24389
24501
|
if (evidence.kind === "review") {
|
|
@@ -24400,6 +24512,11 @@ class WorkitCore {
|
|
|
24400
24512
|
evidence.beforeCandidateId ??= null;
|
|
24401
24513
|
evidence.candidateId ??= null;
|
|
24402
24514
|
}
|
|
24515
|
+
if (helper?.ok) {
|
|
24516
|
+
const assignment = helper.data.data.assignment;
|
|
24517
|
+
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))
|
|
24518
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
24519
|
+
}
|
|
24403
24520
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
24404
24521
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
24405
24522
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -25295,6 +25412,9 @@ class WorkitCore {
|
|
|
25295
25412
|
id: task.id,
|
|
25296
25413
|
revision: task.revision,
|
|
25297
25414
|
workspaceRevision: workspace.data.revision,
|
|
25415
|
+
createdAt: task.createdAt,
|
|
25416
|
+
updatedAt: task.updatedAt,
|
|
25417
|
+
runtime: task.runtime ?? null,
|
|
25298
25418
|
objective: task.intent.data.objective,
|
|
25299
25419
|
status: task.status,
|
|
25300
25420
|
closure: task.closure,
|
|
@@ -25380,7 +25500,7 @@ class WorkitCore {
|
|
|
25380
25500
|
const base = reconcileResume(effectiveView);
|
|
25381
25501
|
if (!base.ok)
|
|
25382
25502
|
return base;
|
|
25383
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
25503
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
25384
25504
|
const blockers = [...base.data.blockers];
|
|
25385
25505
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
25386
25506
|
blockers.push({
|
|
@@ -25447,7 +25567,8 @@ class WorkitCore {
|
|
|
25447
25567
|
...current,
|
|
25448
25568
|
status,
|
|
25449
25569
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
25450
|
-
progress:
|
|
25570
|
+
progress: current.progress,
|
|
25571
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
25451
25572
|
})
|
|
25452
25573
|
});
|
|
25453
25574
|
if (!changed.ok)
|
|
@@ -25483,7 +25604,7 @@ class WorkitCore {
|
|
|
25483
25604
|
return workspace;
|
|
25484
25605
|
if (!workspace.data)
|
|
25485
25606
|
return failure("not_found", "workspace not found");
|
|
25486
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
25607
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
25487
25608
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
25488
25609
|
this.fillRevisions(input, task.data, workspace.data);
|
|
25489
25610
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -25594,15 +25715,15 @@ function resolveGitRevision(root, value) {
|
|
|
25594
25715
|
}
|
|
25595
25716
|
function resolveInside(root, candidate) {
|
|
25596
25717
|
const base = realpathSync5(root);
|
|
25597
|
-
const target =
|
|
25598
|
-
if (target !== base && !target.startsWith(base +
|
|
25718
|
+
const target = path7.resolve(base, candidate);
|
|
25719
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
25599
25720
|
throw new Error("path must stay inside repository root");
|
|
25600
25721
|
}
|
|
25601
25722
|
let ancestor = target;
|
|
25602
|
-
while (!
|
|
25603
|
-
ancestor =
|
|
25723
|
+
while (!existsSync4(ancestor))
|
|
25724
|
+
ancestor = path7.dirname(ancestor);
|
|
25604
25725
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25605
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
25726
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
25606
25727
|
throw new Error("path must stay inside repository root");
|
|
25607
25728
|
}
|
|
25608
25729
|
return target;
|
|
@@ -25787,14 +25908,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25787
25908
|
import {
|
|
25788
25909
|
copyFileSync,
|
|
25789
25910
|
cpSync,
|
|
25790
|
-
existsSync as
|
|
25911
|
+
existsSync as existsSync5,
|
|
25791
25912
|
mkdirSync as mkdirSync2,
|
|
25792
25913
|
readFileSync as readFileSync4,
|
|
25793
25914
|
readdirSync as readdirSync3,
|
|
25794
25915
|
writeFileSync as writeFileSync2
|
|
25795
25916
|
} from "node:fs";
|
|
25796
25917
|
import os2 from "node:os";
|
|
25797
|
-
import
|
|
25918
|
+
import path8 from "node:path";
|
|
25798
25919
|
var diagnosticLogger;
|
|
25799
25920
|
var PRESETS = {
|
|
25800
25921
|
gitflow: {
|
|
@@ -25815,7 +25936,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25815
25936
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25816
25937
|
};
|
|
25817
25938
|
};
|
|
25818
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
25939
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workit");
|
|
25819
25940
|
var migratedDir = null;
|
|
25820
25941
|
var migrationFailed = false;
|
|
25821
25942
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25825,12 +25946,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25825
25946
|
migratedDir = dir;
|
|
25826
25947
|
return dir;
|
|
25827
25948
|
}
|
|
25828
|
-
const legacy =
|
|
25829
|
-
if (!
|
|
25949
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25950
|
+
if (!existsSync5(legacy)) {
|
|
25830
25951
|
migratedDir = dir;
|
|
25831
25952
|
return dir;
|
|
25832
25953
|
}
|
|
25833
|
-
if (!migrationFailed &&
|
|
25954
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25834
25955
|
migratedDir = dir;
|
|
25835
25956
|
return dir;
|
|
25836
25957
|
}
|
|
@@ -25838,9 +25959,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25838
25959
|
mkdirSync2(dir, { recursive: true });
|
|
25839
25960
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25840
25961
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25841
|
-
const src =
|
|
25842
|
-
const dest =
|
|
25843
|
-
if (
|
|
25962
|
+
const src = path8.join(legacy, entry.name);
|
|
25963
|
+
const dest = path8.join(dir, entry.name);
|
|
25964
|
+
if (existsSync5(dest))
|
|
25844
25965
|
continue;
|
|
25845
25966
|
try {
|
|
25846
25967
|
if (entry.isDirectory())
|
|
@@ -25913,7 +26034,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25913
26034
|
};
|
|
25914
26035
|
};
|
|
25915
26036
|
var readConfigTyped = (dir) => {
|
|
25916
|
-
const file =
|
|
26037
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25917
26038
|
return parseConfigResult(readSafe(file), file);
|
|
25918
26039
|
};
|
|
25919
26040
|
var readConfig = () => {
|
|
@@ -25949,9 +26070,9 @@ var COMMIT_PRESETS = [
|
|
|
25949
26070
|
|
|
25950
26071
|
// packages/workit-core/src/core/workspaces.ts
|
|
25951
26072
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25952
|
-
import
|
|
26073
|
+
import path9 from "node:path";
|
|
25953
26074
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25954
|
-
const file =
|
|
26075
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25955
26076
|
let raw;
|
|
25956
26077
|
try {
|
|
25957
26078
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26049,10 +26170,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26049
26170
|
|
|
26050
26171
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26051
26172
|
import fs7 from "node:fs";
|
|
26052
|
-
import
|
|
26173
|
+
import path10 from "node:path";
|
|
26053
26174
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26054
26175
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26055
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26176
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26056
26177
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26057
26178
|
var remoteProvider = (cwd) => {
|
|
26058
26179
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26135,8 +26256,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26135
26256
|
}
|
|
26136
26257
|
const prov = cfg[provider] ?? {};
|
|
26137
26258
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26138
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26139
|
-
const tokenPath =
|
|
26259
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26260
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26140
26261
|
let tokenOk = false;
|
|
26141
26262
|
if (fs7.existsSync(tokenPath)) {
|
|
26142
26263
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26145,7 +26266,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26145
26266
|
}
|
|
26146
26267
|
const out = {
|
|
26147
26268
|
ok: true,
|
|
26148
|
-
configPath:
|
|
26269
|
+
configPath: path10.resolve(cfgPath),
|
|
26149
26270
|
provider,
|
|
26150
26271
|
defaultTargetBranch: defaultTarget,
|
|
26151
26272
|
pr: cfg.pr ?? {},
|
|
@@ -26238,18 +26359,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26238
26359
|
|
|
26239
26360
|
// packages/workit-core/src/core/pr-create.ts
|
|
26240
26361
|
import fs8 from "node:fs";
|
|
26241
|
-
import
|
|
26362
|
+
import path11 from "node:path";
|
|
26242
26363
|
function parseGhIssue(value) {
|
|
26243
26364
|
const m = /issues\/(\d+)/.exec(value);
|
|
26244
26365
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26245
26366
|
}
|
|
26246
26367
|
function whichOnPath(tool) {
|
|
26247
26368
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26248
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26369
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26249
26370
|
if (!dir)
|
|
26250
26371
|
continue;
|
|
26251
26372
|
for (const name of names) {
|
|
26252
|
-
const candidate =
|
|
26373
|
+
const candidate = path11.join(dir, name);
|
|
26253
26374
|
try {
|
|
26254
26375
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26255
26376
|
return candidate;
|
|
@@ -26262,8 +26383,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26262
26383
|
|
|
26263
26384
|
// packages/workit-core/src/core/repo-context.ts
|
|
26264
26385
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26265
|
-
import { existsSync as
|
|
26266
|
-
import
|
|
26386
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26387
|
+
import path12 from "node:path";
|
|
26267
26388
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26268
26389
|
try {
|
|
26269
26390
|
gitRevisionParts(value);
|
|
@@ -26397,7 +26518,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26397
26518
|
]) {
|
|
26398
26519
|
const parts = rel.split("/");
|
|
26399
26520
|
const base = parts[parts.length - 1];
|
|
26400
|
-
const dir = parts.length > 1 ?
|
|
26521
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26401
26522
|
const actual = probeCaseInsensitive(dir, base);
|
|
26402
26523
|
if (actual)
|
|
26403
26524
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26428,8 +26549,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26428
26549
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26429
26550
|
};
|
|
26430
26551
|
var packageScripts = (cwd, keys) => {
|
|
26431
|
-
const pkgPath =
|
|
26432
|
-
if (!
|
|
26552
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26553
|
+
if (!existsSync6(pkgPath))
|
|
26433
26554
|
return [];
|
|
26434
26555
|
try {
|
|
26435
26556
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26454,9 +26575,9 @@ var documentationFiles = (cwd) => {
|
|
|
26454
26575
|
if (entry.isDirectory()) {
|
|
26455
26576
|
if (excluded.has(entry.name))
|
|
26456
26577
|
continue;
|
|
26457
|
-
walk(
|
|
26578
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26458
26579
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26459
|
-
matches.push("./" +
|
|
26580
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26460
26581
|
}
|
|
26461
26582
|
}
|
|
26462
26583
|
};
|
|
@@ -26464,7 +26585,7 @@ var documentationFiles = (cwd) => {
|
|
|
26464
26585
|
return matches.sort().slice(0, 200);
|
|
26465
26586
|
};
|
|
26466
26587
|
var readTrimmed = (file, maxLines) => {
|
|
26467
|
-
if (!
|
|
26588
|
+
if (!existsSync6(file))
|
|
26468
26589
|
return "";
|
|
26469
26590
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26470
26591
|
`);
|
|
@@ -26472,7 +26593,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26472
26593
|
`);
|
|
26473
26594
|
};
|
|
26474
26595
|
function prReadyContext(root, range) {
|
|
26475
|
-
const cwd =
|
|
26596
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26476
26597
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26477
26598
|
return invalidRange(cwd);
|
|
26478
26599
|
let stdout = "";
|
|
@@ -26530,7 +26651,7 @@ ${status.stderr}` : "");
|
|
|
26530
26651
|
stdout += `template_path: ${template}
|
|
26531
26652
|
|
|
26532
26653
|
`;
|
|
26533
|
-
stdout += readTrimmed(
|
|
26654
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26534
26655
|
} else {
|
|
26535
26656
|
stdout += `template_path: none
|
|
26536
26657
|
|
|
@@ -26540,7 +26661,7 @@ ${status.stderr}` : "");
|
|
|
26540
26661
|
stdout += `
|
|
26541
26662
|
`;
|
|
26542
26663
|
stdout += printSection("Recent Validation Signals");
|
|
26543
|
-
if (
|
|
26664
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26544
26665
|
stdout += `package.json detected. Common scripts:
|
|
26545
26666
|
`;
|
|
26546
26667
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26549,7 +26670,7 @@ ${status.stderr}` : "");
|
|
|
26549
26670
|
`) + `
|
|
26550
26671
|
`;
|
|
26551
26672
|
}
|
|
26552
|
-
if (
|
|
26673
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26553
26674
|
stdout += `Rust project detected.
|
|
26554
26675
|
`;
|
|
26555
26676
|
}
|
|
@@ -26582,7 +26703,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26582
26703
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26583
26704
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26584
26705
|
function changelogContext(root, range) {
|
|
26585
|
-
const cwd =
|
|
26706
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26586
26707
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26587
26708
|
return invalidRange(cwd);
|
|
26588
26709
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26600,8 +26721,8 @@ function changelogContext(root, range) {
|
|
|
26600
26721
|
stdout += CHANGELOG_RULES + `
|
|
26601
26722
|
`;
|
|
26602
26723
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26603
|
-
const changelogPath =
|
|
26604
|
-
if (
|
|
26724
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26725
|
+
if (existsSync6(changelogPath)) {
|
|
26605
26726
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26606
26727
|
`;
|
|
26607
26728
|
} else {
|
|
@@ -26620,7 +26741,7 @@ function changelogContext(root, range) {
|
|
|
26620
26741
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26621
26742
|
}
|
|
26622
26743
|
function docsRefreshContext(root, range) {
|
|
26623
|
-
const cwd =
|
|
26744
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26624
26745
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26625
26746
|
return invalidRange(cwd);
|
|
26626
26747
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26642,8 +26763,8 @@ function docsRefreshContext(root, range) {
|
|
|
26642
26763
|
`) + `
|
|
26643
26764
|
`;
|
|
26644
26765
|
stdout += printSection("README Preview");
|
|
26645
|
-
const readme =
|
|
26646
|
-
if (
|
|
26766
|
+
const readme = path12.join(cwd, "README.md");
|
|
26767
|
+
if (existsSync6(readme)) {
|
|
26647
26768
|
stdout += readTrimmed(readme, 220) + `
|
|
26648
26769
|
`;
|
|
26649
26770
|
} else {
|
|
@@ -26651,8 +26772,8 @@ function docsRefreshContext(root, range) {
|
|
|
26651
26772
|
`;
|
|
26652
26773
|
}
|
|
26653
26774
|
stdout += printSection("Package Scripts");
|
|
26654
|
-
const pkgPath =
|
|
26655
|
-
if (
|
|
26775
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26776
|
+
if (existsSync6(pkgPath)) {
|
|
26656
26777
|
try {
|
|
26657
26778
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26658
26779
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26665,7 +26786,7 @@ function docsRefreshContext(root, range) {
|
|
|
26665
26786
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26666
26787
|
}
|
|
26667
26788
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26668
|
-
const cwd =
|
|
26789
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26669
26790
|
if (!rangeOrTag) {
|
|
26670
26791
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26671
26792
|
`, exitCode: 1, cwd };
|
|
@@ -26703,7 +26824,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26703
26824
|
`;
|
|
26704
26825
|
stdout += printSection("Existing Release Files");
|
|
26705
26826
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26706
|
-
if (
|
|
26827
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26707
26828
|
stdout += rel + `
|
|
26708
26829
|
`;
|
|
26709
26830
|
}
|
|
@@ -26894,25 +27015,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26894
27015
|
import fs10 from "node:fs";
|
|
26895
27016
|
import path13 from "node:path";
|
|
26896
27017
|
|
|
26897
|
-
// packages/workit-core/src/core/package-root.ts
|
|
26898
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
26899
|
-
import path12 from "node:path";
|
|
26900
|
-
import { fileURLToPath } from "node:url";
|
|
26901
|
-
var packageRoot = () => {
|
|
26902
|
-
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
26903
|
-
while (true) {
|
|
26904
|
-
const parent = path12.dirname(dir);
|
|
26905
|
-
if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
|
|
26906
|
-
return dir;
|
|
26907
|
-
dir = parent;
|
|
26908
|
-
}
|
|
26909
|
-
};
|
|
26910
|
-
var assetRoot = () => {
|
|
26911
|
-
const root = packageRoot();
|
|
26912
|
-
const assets = path12.join(root, "assets");
|
|
26913
|
-
return existsSync6(assets) ? assets : root;
|
|
26914
|
-
};
|
|
26915
|
-
|
|
26916
27018
|
// packages/workit-core/src/core/templates.ts
|
|
26917
27019
|
var repoRoot2 = assetRoot();
|
|
26918
27020
|
|
|
@@ -27416,6 +27518,32 @@ var toolInputSchema = (family) => {
|
|
|
27416
27518
|
const schema = boundedOperationJsonSchema(family);
|
|
27417
27519
|
return { type: "object", ...schema };
|
|
27418
27520
|
};
|
|
27521
|
+
var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
|
|
27522
|
+
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
27523
|
+
const action = branch.properties?.action?.const;
|
|
27524
|
+
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
27525
|
+
});
|
|
27526
|
+
var advertisedToolSchemas = (attested) => {
|
|
27527
|
+
if (attested)
|
|
27528
|
+
return OPERATION_FAMILIES.map((family) => ({
|
|
27529
|
+
name: `workit_${family}`,
|
|
27530
|
+
description: operationDescription(family),
|
|
27531
|
+
inputSchema: toolInputSchema(family)
|
|
27532
|
+
}));
|
|
27533
|
+
return OPERATION_FAMILIES.flatMap((family) => {
|
|
27534
|
+
const branches = readOnlyBranches(family);
|
|
27535
|
+
if (!branches.length)
|
|
27536
|
+
return [];
|
|
27537
|
+
const schema = boundedOperationJsonSchema(family);
|
|
27538
|
+
return [
|
|
27539
|
+
{
|
|
27540
|
+
name: `workit_${family}`,
|
|
27541
|
+
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27542
|
+
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
27543
|
+
}
|
|
27544
|
+
];
|
|
27545
|
+
});
|
|
27546
|
+
};
|
|
27419
27547
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27420
27548
|
if (result.ok)
|
|
27421
27549
|
return result;
|
|
@@ -27470,13 +27598,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27470
27598
|
].filter((file) => existsSync7(file)));
|
|
27471
27599
|
let staleWarned = false;
|
|
27472
27600
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27473
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
|
|
27477
|
-
|
|
27478
|
-
}
|
|
27479
|
-
|
|
27601
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27602
|
+
let attested = false;
|
|
27603
|
+
try {
|
|
27604
|
+
const context = await contextProvider.current();
|
|
27605
|
+
attested = context.callerAttested === true;
|
|
27606
|
+
} catch {
|
|
27607
|
+
attested = false;
|
|
27608
|
+
}
|
|
27609
|
+
return { tools: advertisedToolSchemas(attested) };
|
|
27610
|
+
});
|
|
27480
27611
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27481
27612
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27482
27613
|
uri: `workit://context/${kind}`,
|
|
@@ -27591,7 +27722,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27591
27722
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27592
27723
|
if (!parsed.ok)
|
|
27593
27724
|
return resultForClient(parsed, workspaceRoot);
|
|
27594
|
-
if (context.callerAttested
|
|
27725
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27595
27726
|
return resultForClient({
|
|
27596
27727
|
ok: false,
|
|
27597
27728
|
schemaVersion: 1,
|
|
@@ -27680,6 +27811,13 @@ var cursorCapabilities = (availability = {}) => {
|
|
|
27680
27811
|
reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
|
|
27681
27812
|
refs: [hostRef("subagentStart")]
|
|
27682
27813
|
},
|
|
27814
|
+
{
|
|
27815
|
+
name: "fresh-context-review",
|
|
27816
|
+
surface: "subagentStart",
|
|
27817
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
27818
|
+
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",
|
|
27819
|
+
refs: [hostRef("subagentStart")]
|
|
27820
|
+
},
|
|
27683
27821
|
{
|
|
27684
27822
|
name: "arbitrary_shell_write",
|
|
27685
27823
|
surface: "unobservable_shell",
|