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