@brainervirus/workit-cursor 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +388 -195
- package/dist/mcp-server.js +364 -221
- package/dist/workit-hook.js +386 -194
- package/package.json +3 -3
- package/skills/workit-babysit/SKILL.md +3 -1
- 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,
|
|
@@ -20310,7 +20311,7 @@ var workerReportSchema = object4({
|
|
|
20310
20311
|
}).strict();
|
|
20311
20312
|
var workerSchema = object4({
|
|
20312
20313
|
assignment: assignmentSchema,
|
|
20313
|
-
state: _enum2(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
20314
|
+
state: _enum2(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
20314
20315
|
session: refSchema.nullable(),
|
|
20315
20316
|
report: workerReportSchema.nullable()
|
|
20316
20317
|
}).strict();
|
|
@@ -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,
|
|
@@ -23752,10 +23864,10 @@ var applyWorkerLifecycle = (input) => {
|
|
|
23752
23864
|
const notStarted = input.dispatch === true;
|
|
23753
23865
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
23754
23866
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
23755
|
-
if (notStarted &&
|
|
23756
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
23757
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
23867
|
+
if (notStarted && entry.data.session !== null)
|
|
23758
23868
|
return failure("invalid_transition", "worker already has an observed session");
|
|
23869
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
23870
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
23759
23871
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
23760
23872
|
return failure("invalid_input", "running worker observations require a session");
|
|
23761
23873
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -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))
|
|
@@ -24906,7 +25023,12 @@ class WorkitCore {
|
|
|
24906
25023
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
24907
25024
|
return success(mutation.revision, null, {
|
|
24908
25025
|
...current,
|
|
24909
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
25026
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
25027
|
+
...item,
|
|
25028
|
+
recordedAt: mutation.now,
|
|
25029
|
+
provenance: attested.data,
|
|
25030
|
+
data: { ...item.data, state: "dispatching" }
|
|
25031
|
+
} : item)
|
|
24910
25032
|
});
|
|
24911
25033
|
}
|
|
24912
25034
|
});
|
|
@@ -25290,6 +25412,9 @@ class WorkitCore {
|
|
|
25290
25412
|
id: task.id,
|
|
25291
25413
|
revision: task.revision,
|
|
25292
25414
|
workspaceRevision: workspace.data.revision,
|
|
25415
|
+
createdAt: task.createdAt,
|
|
25416
|
+
updatedAt: task.updatedAt,
|
|
25417
|
+
runtime: task.runtime ?? null,
|
|
25293
25418
|
objective: task.intent.data.objective,
|
|
25294
25419
|
status: task.status,
|
|
25295
25420
|
closure: task.closure,
|
|
@@ -25375,7 +25500,7 @@ class WorkitCore {
|
|
|
25375
25500
|
const base = reconcileResume(effectiveView);
|
|
25376
25501
|
if (!base.ok)
|
|
25377
25502
|
return base;
|
|
25378
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
25503
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
25379
25504
|
const blockers = [...base.data.blockers];
|
|
25380
25505
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
25381
25506
|
blockers.push({
|
|
@@ -25442,7 +25567,8 @@ class WorkitCore {
|
|
|
25442
25567
|
...current,
|
|
25443
25568
|
status,
|
|
25444
25569
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
25445
|
-
progress:
|
|
25570
|
+
progress: current.progress,
|
|
25571
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
25446
25572
|
})
|
|
25447
25573
|
});
|
|
25448
25574
|
if (!changed.ok)
|
|
@@ -25478,7 +25604,7 @@ class WorkitCore {
|
|
|
25478
25604
|
return workspace;
|
|
25479
25605
|
if (!workspace.data)
|
|
25480
25606
|
return failure("not_found", "workspace not found");
|
|
25481
|
-
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)))
|
|
25482
25608
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
25483
25609
|
this.fillRevisions(input, task.data, workspace.data);
|
|
25484
25610
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -25589,15 +25715,15 @@ function resolveGitRevision(root, value) {
|
|
|
25589
25715
|
}
|
|
25590
25716
|
function resolveInside(root, candidate) {
|
|
25591
25717
|
const base = realpathSync5(root);
|
|
25592
|
-
const target =
|
|
25593
|
-
if (target !== base && !target.startsWith(base +
|
|
25718
|
+
const target = path7.resolve(base, candidate);
|
|
25719
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
25594
25720
|
throw new Error("path must stay inside repository root");
|
|
25595
25721
|
}
|
|
25596
25722
|
let ancestor = target;
|
|
25597
|
-
while (!
|
|
25598
|
-
ancestor =
|
|
25723
|
+
while (!existsSync4(ancestor))
|
|
25724
|
+
ancestor = path7.dirname(ancestor);
|
|
25599
25725
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25600
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
25726
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
25601
25727
|
throw new Error("path must stay inside repository root");
|
|
25602
25728
|
}
|
|
25603
25729
|
return target;
|
|
@@ -25782,14 +25908,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25782
25908
|
import {
|
|
25783
25909
|
copyFileSync,
|
|
25784
25910
|
cpSync,
|
|
25785
|
-
existsSync as
|
|
25911
|
+
existsSync as existsSync5,
|
|
25786
25912
|
mkdirSync as mkdirSync2,
|
|
25787
25913
|
readFileSync as readFileSync4,
|
|
25788
25914
|
readdirSync as readdirSync3,
|
|
25789
25915
|
writeFileSync as writeFileSync2
|
|
25790
25916
|
} from "node:fs";
|
|
25791
25917
|
import os2 from "node:os";
|
|
25792
|
-
import
|
|
25918
|
+
import path8 from "node:path";
|
|
25793
25919
|
var diagnosticLogger;
|
|
25794
25920
|
var PRESETS = {
|
|
25795
25921
|
gitflow: {
|
|
@@ -25810,7 +25936,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25810
25936
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25811
25937
|
};
|
|
25812
25938
|
};
|
|
25813
|
-
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");
|
|
25814
25940
|
var migratedDir = null;
|
|
25815
25941
|
var migrationFailed = false;
|
|
25816
25942
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25820,12 +25946,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25820
25946
|
migratedDir = dir;
|
|
25821
25947
|
return dir;
|
|
25822
25948
|
}
|
|
25823
|
-
const legacy =
|
|
25824
|
-
if (!
|
|
25949
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25950
|
+
if (!existsSync5(legacy)) {
|
|
25825
25951
|
migratedDir = dir;
|
|
25826
25952
|
return dir;
|
|
25827
25953
|
}
|
|
25828
|
-
if (!migrationFailed &&
|
|
25954
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25829
25955
|
migratedDir = dir;
|
|
25830
25956
|
return dir;
|
|
25831
25957
|
}
|
|
@@ -25833,9 +25959,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25833
25959
|
mkdirSync2(dir, { recursive: true });
|
|
25834
25960
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25835
25961
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25836
|
-
const src =
|
|
25837
|
-
const dest =
|
|
25838
|
-
if (
|
|
25962
|
+
const src = path8.join(legacy, entry.name);
|
|
25963
|
+
const dest = path8.join(dir, entry.name);
|
|
25964
|
+
if (existsSync5(dest))
|
|
25839
25965
|
continue;
|
|
25840
25966
|
try {
|
|
25841
25967
|
if (entry.isDirectory())
|
|
@@ -25908,7 +26034,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25908
26034
|
};
|
|
25909
26035
|
};
|
|
25910
26036
|
var readConfigTyped = (dir) => {
|
|
25911
|
-
const file =
|
|
26037
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25912
26038
|
return parseConfigResult(readSafe(file), file);
|
|
25913
26039
|
};
|
|
25914
26040
|
var readConfig = () => {
|
|
@@ -25944,9 +26070,9 @@ var COMMIT_PRESETS = [
|
|
|
25944
26070
|
|
|
25945
26071
|
// packages/workit-core/src/core/workspaces.ts
|
|
25946
26072
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25947
|
-
import
|
|
26073
|
+
import path9 from "node:path";
|
|
25948
26074
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25949
|
-
const file =
|
|
26075
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25950
26076
|
let raw;
|
|
25951
26077
|
try {
|
|
25952
26078
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26044,10 +26170,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26044
26170
|
|
|
26045
26171
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26046
26172
|
import fs7 from "node:fs";
|
|
26047
|
-
import
|
|
26173
|
+
import path10 from "node:path";
|
|
26048
26174
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26049
26175
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26050
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26176
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26051
26177
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26052
26178
|
var remoteProvider = (cwd) => {
|
|
26053
26179
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26130,8 +26256,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26130
26256
|
}
|
|
26131
26257
|
const prov = cfg[provider] ?? {};
|
|
26132
26258
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26133
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26134
|
-
const tokenPath =
|
|
26259
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26260
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26135
26261
|
let tokenOk = false;
|
|
26136
26262
|
if (fs7.existsSync(tokenPath)) {
|
|
26137
26263
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26140,7 +26266,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26140
26266
|
}
|
|
26141
26267
|
const out = {
|
|
26142
26268
|
ok: true,
|
|
26143
|
-
configPath:
|
|
26269
|
+
configPath: path10.resolve(cfgPath),
|
|
26144
26270
|
provider,
|
|
26145
26271
|
defaultTargetBranch: defaultTarget,
|
|
26146
26272
|
pr: cfg.pr ?? {},
|
|
@@ -26233,18 +26359,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26233
26359
|
|
|
26234
26360
|
// packages/workit-core/src/core/pr-create.ts
|
|
26235
26361
|
import fs8 from "node:fs";
|
|
26236
|
-
import
|
|
26362
|
+
import path11 from "node:path";
|
|
26237
26363
|
function parseGhIssue(value) {
|
|
26238
26364
|
const m = /issues\/(\d+)/.exec(value);
|
|
26239
26365
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26240
26366
|
}
|
|
26241
26367
|
function whichOnPath(tool) {
|
|
26242
26368
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26243
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26369
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26244
26370
|
if (!dir)
|
|
26245
26371
|
continue;
|
|
26246
26372
|
for (const name of names) {
|
|
26247
|
-
const candidate =
|
|
26373
|
+
const candidate = path11.join(dir, name);
|
|
26248
26374
|
try {
|
|
26249
26375
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26250
26376
|
return candidate;
|
|
@@ -26257,8 +26383,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26257
26383
|
|
|
26258
26384
|
// packages/workit-core/src/core/repo-context.ts
|
|
26259
26385
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26260
|
-
import { existsSync as
|
|
26261
|
-
import
|
|
26386
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26387
|
+
import path12 from "node:path";
|
|
26262
26388
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26263
26389
|
try {
|
|
26264
26390
|
gitRevisionParts(value);
|
|
@@ -26392,7 +26518,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26392
26518
|
]) {
|
|
26393
26519
|
const parts = rel.split("/");
|
|
26394
26520
|
const base = parts[parts.length - 1];
|
|
26395
|
-
const dir = parts.length > 1 ?
|
|
26521
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26396
26522
|
const actual = probeCaseInsensitive(dir, base);
|
|
26397
26523
|
if (actual)
|
|
26398
26524
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26423,8 +26549,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26423
26549
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26424
26550
|
};
|
|
26425
26551
|
var packageScripts = (cwd, keys) => {
|
|
26426
|
-
const pkgPath =
|
|
26427
|
-
if (!
|
|
26552
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26553
|
+
if (!existsSync6(pkgPath))
|
|
26428
26554
|
return [];
|
|
26429
26555
|
try {
|
|
26430
26556
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26449,9 +26575,9 @@ var documentationFiles = (cwd) => {
|
|
|
26449
26575
|
if (entry.isDirectory()) {
|
|
26450
26576
|
if (excluded.has(entry.name))
|
|
26451
26577
|
continue;
|
|
26452
|
-
walk(
|
|
26578
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26453
26579
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26454
|
-
matches.push("./" +
|
|
26580
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26455
26581
|
}
|
|
26456
26582
|
}
|
|
26457
26583
|
};
|
|
@@ -26459,7 +26585,7 @@ var documentationFiles = (cwd) => {
|
|
|
26459
26585
|
return matches.sort().slice(0, 200);
|
|
26460
26586
|
};
|
|
26461
26587
|
var readTrimmed = (file, maxLines) => {
|
|
26462
|
-
if (!
|
|
26588
|
+
if (!existsSync6(file))
|
|
26463
26589
|
return "";
|
|
26464
26590
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26465
26591
|
`);
|
|
@@ -26467,7 +26593,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26467
26593
|
`);
|
|
26468
26594
|
};
|
|
26469
26595
|
function prReadyContext(root, range) {
|
|
26470
|
-
const cwd =
|
|
26596
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26471
26597
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26472
26598
|
return invalidRange(cwd);
|
|
26473
26599
|
let stdout = "";
|
|
@@ -26525,7 +26651,7 @@ ${status.stderr}` : "");
|
|
|
26525
26651
|
stdout += `template_path: ${template}
|
|
26526
26652
|
|
|
26527
26653
|
`;
|
|
26528
|
-
stdout += readTrimmed(
|
|
26654
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26529
26655
|
} else {
|
|
26530
26656
|
stdout += `template_path: none
|
|
26531
26657
|
|
|
@@ -26535,7 +26661,7 @@ ${status.stderr}` : "");
|
|
|
26535
26661
|
stdout += `
|
|
26536
26662
|
`;
|
|
26537
26663
|
stdout += printSection("Recent Validation Signals");
|
|
26538
|
-
if (
|
|
26664
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26539
26665
|
stdout += `package.json detected. Common scripts:
|
|
26540
26666
|
`;
|
|
26541
26667
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26544,7 +26670,7 @@ ${status.stderr}` : "");
|
|
|
26544
26670
|
`) + `
|
|
26545
26671
|
`;
|
|
26546
26672
|
}
|
|
26547
|
-
if (
|
|
26673
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26548
26674
|
stdout += `Rust project detected.
|
|
26549
26675
|
`;
|
|
26550
26676
|
}
|
|
@@ -26577,7 +26703,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26577
26703
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26578
26704
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26579
26705
|
function changelogContext(root, range) {
|
|
26580
|
-
const cwd =
|
|
26706
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26581
26707
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26582
26708
|
return invalidRange(cwd);
|
|
26583
26709
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26595,8 +26721,8 @@ function changelogContext(root, range) {
|
|
|
26595
26721
|
stdout += CHANGELOG_RULES + `
|
|
26596
26722
|
`;
|
|
26597
26723
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26598
|
-
const changelogPath =
|
|
26599
|
-
if (
|
|
26724
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26725
|
+
if (existsSync6(changelogPath)) {
|
|
26600
26726
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26601
26727
|
`;
|
|
26602
26728
|
} else {
|
|
@@ -26615,7 +26741,7 @@ function changelogContext(root, range) {
|
|
|
26615
26741
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26616
26742
|
}
|
|
26617
26743
|
function docsRefreshContext(root, range) {
|
|
26618
|
-
const cwd =
|
|
26744
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26619
26745
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26620
26746
|
return invalidRange(cwd);
|
|
26621
26747
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26637,8 +26763,8 @@ function docsRefreshContext(root, range) {
|
|
|
26637
26763
|
`) + `
|
|
26638
26764
|
`;
|
|
26639
26765
|
stdout += printSection("README Preview");
|
|
26640
|
-
const readme =
|
|
26641
|
-
if (
|
|
26766
|
+
const readme = path12.join(cwd, "README.md");
|
|
26767
|
+
if (existsSync6(readme)) {
|
|
26642
26768
|
stdout += readTrimmed(readme, 220) + `
|
|
26643
26769
|
`;
|
|
26644
26770
|
} else {
|
|
@@ -26646,8 +26772,8 @@ function docsRefreshContext(root, range) {
|
|
|
26646
26772
|
`;
|
|
26647
26773
|
}
|
|
26648
26774
|
stdout += printSection("Package Scripts");
|
|
26649
|
-
const pkgPath =
|
|
26650
|
-
if (
|
|
26775
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26776
|
+
if (existsSync6(pkgPath)) {
|
|
26651
26777
|
try {
|
|
26652
26778
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26653
26779
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26660,7 +26786,7 @@ function docsRefreshContext(root, range) {
|
|
|
26660
26786
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26661
26787
|
}
|
|
26662
26788
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26663
|
-
const cwd =
|
|
26789
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26664
26790
|
if (!rangeOrTag) {
|
|
26665
26791
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26666
26792
|
`, exitCode: 1, cwd };
|
|
@@ -26698,7 +26824,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26698
26824
|
`;
|
|
26699
26825
|
stdout += printSection("Existing Release Files");
|
|
26700
26826
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26701
|
-
if (
|
|
26827
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26702
26828
|
stdout += rel + `
|
|
26703
26829
|
`;
|
|
26704
26830
|
}
|
|
@@ -26889,25 +27015,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26889
27015
|
import fs10 from "node:fs";
|
|
26890
27016
|
import path13 from "node:path";
|
|
26891
27017
|
|
|
26892
|
-
// packages/workit-core/src/core/package-root.ts
|
|
26893
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
26894
|
-
import path12 from "node:path";
|
|
26895
|
-
import { fileURLToPath } from "node:url";
|
|
26896
|
-
var packageRoot = () => {
|
|
26897
|
-
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
26898
|
-
while (true) {
|
|
26899
|
-
const parent = path12.dirname(dir);
|
|
26900
|
-
if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
|
|
26901
|
-
return dir;
|
|
26902
|
-
dir = parent;
|
|
26903
|
-
}
|
|
26904
|
-
};
|
|
26905
|
-
var assetRoot = () => {
|
|
26906
|
-
const root = packageRoot();
|
|
26907
|
-
const assets = path12.join(root, "assets");
|
|
26908
|
-
return existsSync6(assets) ? assets : root;
|
|
26909
|
-
};
|
|
26910
|
-
|
|
26911
27018
|
// packages/workit-core/src/core/templates.ts
|
|
26912
27019
|
var repoRoot2 = assetRoot();
|
|
26913
27020
|
|
|
@@ -27411,6 +27518,32 @@ var toolInputSchema = (family) => {
|
|
|
27411
27518
|
const schema = boundedOperationJsonSchema(family);
|
|
27412
27519
|
return { type: "object", ...schema };
|
|
27413
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
|
+
};
|
|
27414
27547
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27415
27548
|
if (result.ok)
|
|
27416
27549
|
return result;
|
|
@@ -27465,13 +27598,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27465
27598
|
].filter((file) => existsSync7(file)));
|
|
27466
27599
|
let staleWarned = false;
|
|
27467
27600
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27468
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
|
|
27472
|
-
|
|
27473
|
-
}
|
|
27474
|
-
|
|
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
|
+
});
|
|
27475
27611
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27476
27612
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27477
27613
|
uri: `workit://context/${kind}`,
|
|
@@ -27586,7 +27722,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27586
27722
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27587
27723
|
if (!parsed.ok)
|
|
27588
27724
|
return resultForClient(parsed, workspaceRoot);
|
|
27589
|
-
if (context.callerAttested
|
|
27725
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27590
27726
|
return resultForClient({
|
|
27591
27727
|
ok: false,
|
|
27592
27728
|
schemaVersion: 1,
|
|
@@ -27675,6 +27811,13 @@ var cursorCapabilities = (availability = {}) => {
|
|
|
27675
27811
|
reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
|
|
27676
27812
|
refs: [hostRef("subagentStart")]
|
|
27677
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
|
+
},
|
|
27678
27821
|
{
|
|
27679
27822
|
name: "arbitrary_shell_write",
|
|
27680
27823
|
surface: "unobservable_shell",
|