@brainervirus/workit-mcp 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +359 -222
- 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,10 +20270,12 @@ var decisionSchema = object4({
|
|
|
20270
20270
|
scope: scopeSchema,
|
|
20271
20271
|
presented: text,
|
|
20272
20272
|
approvedContent: text,
|
|
20273
|
-
|
|
20273
|
+
displayed: text.optional(),
|
|
20274
|
+
contentRefs: array3(refSchema),
|
|
20275
|
+
statedChoice: object4({ ref: text, text }).strict().optional()
|
|
20274
20276
|
}).strict(),
|
|
20275
20277
|
digest,
|
|
20276
|
-
response: _enum2(["approved", "rejected"]),
|
|
20278
|
+
response: _enum2(["approved", "rejected", "stated"]),
|
|
20277
20279
|
requirementIds: array3(digest),
|
|
20278
20280
|
revoked: object4({ at: utc, reason: text }).strict().nullable(),
|
|
20279
20281
|
consumption: object4({
|
|
@@ -20348,6 +20350,10 @@ var actionProgressListSchema = array3(actionProgressSchema).check((ctx) => {
|
|
|
20348
20350
|
path: ["decisionId"]
|
|
20349
20351
|
});
|
|
20350
20352
|
});
|
|
20353
|
+
var runtimeSchema = object4({
|
|
20354
|
+
createdWith: text.nullable(),
|
|
20355
|
+
updatedWith: text
|
|
20356
|
+
}).strict();
|
|
20351
20357
|
var taskRecordSchema = object4({
|
|
20352
20358
|
schemaVersion: literal3(1),
|
|
20353
20359
|
id,
|
|
@@ -20361,6 +20367,8 @@ var taskRecordSchema = object4({
|
|
|
20361
20367
|
status: _enum2(["active", "paused", "closed"]),
|
|
20362
20368
|
closure: closureSchema.nullable(),
|
|
20363
20369
|
progress: progressSchema,
|
|
20370
|
+
pauseReason: text.nullable().optional(),
|
|
20371
|
+
runtime: runtimeSchema.optional(),
|
|
20364
20372
|
assessments: array3(entrySchema(assessmentSchema)),
|
|
20365
20373
|
policy: policySchema.nullable(),
|
|
20366
20374
|
policyChanges: array3(policyChangeSchema),
|
|
@@ -20376,6 +20384,7 @@ var workspaceRecordSchema = object4({
|
|
|
20376
20384
|
id,
|
|
20377
20385
|
revision,
|
|
20378
20386
|
root: nonEmpty,
|
|
20387
|
+
runtime: runtimeSchema.optional(),
|
|
20379
20388
|
writer: object4({ state: _enum2(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
20380
20389
|
}).strict();
|
|
20381
20390
|
var capabilitySchema = object4({
|
|
@@ -20403,6 +20412,9 @@ var taskSummarySchema = object4({
|
|
|
20403
20412
|
id,
|
|
20404
20413
|
revision,
|
|
20405
20414
|
workspaceRevision: revision,
|
|
20415
|
+
createdAt: utc,
|
|
20416
|
+
updatedAt: utc,
|
|
20417
|
+
runtime: runtimeSchema.nullable(),
|
|
20406
20418
|
objective: text,
|
|
20407
20419
|
status: _enum2(["active", "paused", "closed"]),
|
|
20408
20420
|
closure: closureSchema.nullable(),
|
|
@@ -20473,7 +20485,7 @@ var taskOperations = {
|
|
|
20473
20485
|
expectedWorkspaceRevision: revision.optional(),
|
|
20474
20486
|
outcome: outcomeSchema,
|
|
20475
20487
|
summary: text,
|
|
20476
|
-
decisionIds: array3(id)
|
|
20488
|
+
decisionIds: array3(id).optional()
|
|
20477
20489
|
})
|
|
20478
20490
|
};
|
|
20479
20491
|
var policyOperations = {
|
|
@@ -20565,14 +20577,15 @@ var writerOperations = {
|
|
|
20565
20577
|
...taskId,
|
|
20566
20578
|
expectedRevision: revision.optional(),
|
|
20567
20579
|
expectedWorkspaceRevision: revision.optional(),
|
|
20568
|
-
workerId: nullableId.optional()
|
|
20580
|
+
workerId: nullableId.optional(),
|
|
20581
|
+
reason: text.optional()
|
|
20569
20582
|
}),
|
|
20570
20583
|
release: operation({
|
|
20571
20584
|
action: literal3("release"),
|
|
20572
20585
|
...taskId,
|
|
20573
20586
|
expectedRevision: revision.optional(),
|
|
20574
20587
|
expectedWorkspaceRevision: revision.optional(),
|
|
20575
|
-
reason: text
|
|
20588
|
+
reason: text.optional()
|
|
20576
20589
|
})
|
|
20577
20590
|
};
|
|
20578
20591
|
var stateOperations = {
|
|
@@ -20779,7 +20792,26 @@ function requirementId(input) {
|
|
|
20779
20792
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
20780
20793
|
import * as fs4 from "node:fs";
|
|
20781
20794
|
import { hostname as hostname2 } from "node:os";
|
|
20782
|
-
import
|
|
20795
|
+
import path4 from "node:path";
|
|
20796
|
+
|
|
20797
|
+
// packages/workit-core/src/core/package-root.ts
|
|
20798
|
+
import { existsSync } from "node:fs";
|
|
20799
|
+
import path from "node:path";
|
|
20800
|
+
import { fileURLToPath } from "node:url";
|
|
20801
|
+
var packageRoot = () => {
|
|
20802
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
20803
|
+
while (true) {
|
|
20804
|
+
const parent = path.dirname(dir);
|
|
20805
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
20806
|
+
return dir;
|
|
20807
|
+
dir = parent;
|
|
20808
|
+
}
|
|
20809
|
+
};
|
|
20810
|
+
var assetRoot = () => {
|
|
20811
|
+
const root = packageRoot();
|
|
20812
|
+
const assets = path.join(root, "assets");
|
|
20813
|
+
return existsSync(assets) ? assets : root;
|
|
20814
|
+
};
|
|
20783
20815
|
|
|
20784
20816
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
20785
20817
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -20830,7 +20862,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
20830
20862
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
20831
20863
|
import { randomBytes } from "node:crypto";
|
|
20832
20864
|
import fsSync from "node:fs";
|
|
20833
|
-
import
|
|
20865
|
+
import path2 from "node:path";
|
|
20834
20866
|
|
|
20835
20867
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
20836
20868
|
import fs from "node:fs";
|
|
@@ -20920,13 +20952,13 @@ ${ownershipToken}
|
|
|
20920
20952
|
return { raw, ownershipToken };
|
|
20921
20953
|
}
|
|
20922
20954
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
20923
|
-
const resolved =
|
|
20924
|
-
const lexicalRelative =
|
|
20925
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
20926
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
20955
|
+
const resolved = path2.resolve(lockPath);
|
|
20956
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
20957
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
20958
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
20927
20959
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
20928
20960
|
}
|
|
20929
|
-
return relative.split(
|
|
20961
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
20930
20962
|
}
|
|
20931
20963
|
function parseSidecarLockPayload(raw, parser) {
|
|
20932
20964
|
if (parser) {
|
|
@@ -21073,7 +21105,7 @@ function getFsSafeLockConfig() {
|
|
|
21073
21105
|
|
|
21074
21106
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
21075
21107
|
import fs3 from "node:fs";
|
|
21076
|
-
import
|
|
21108
|
+
import path3 from "node:path";
|
|
21077
21109
|
|
|
21078
21110
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
21079
21111
|
function sleepSync(ms) {
|
|
@@ -21164,26 +21196,26 @@ function canonicalLockParentSync(parent) {
|
|
|
21164
21196
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
21165
21197
|
}
|
|
21166
21198
|
function normalizeTargetPath(targetPath) {
|
|
21167
|
-
const resolved =
|
|
21168
|
-
fs3.mkdirSync(
|
|
21199
|
+
const resolved = path3.resolve(targetPath);
|
|
21200
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
21169
21201
|
try {
|
|
21170
|
-
return
|
|
21202
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
21171
21203
|
} catch {
|
|
21172
21204
|
return resolved;
|
|
21173
21205
|
}
|
|
21174
21206
|
}
|
|
21175
21207
|
function boundedLockPath(lockPath, lockRoot) {
|
|
21176
|
-
const resolved =
|
|
21208
|
+
const resolved = path3.resolve(lockPath);
|
|
21177
21209
|
if (!lockRoot)
|
|
21178
21210
|
return resolved;
|
|
21179
21211
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
21180
|
-
const parent =
|
|
21212
|
+
const parent = path3.dirname(resolved);
|
|
21181
21213
|
const parentReal = canonicalLockParentSync(parent);
|
|
21182
|
-
const parentRelative =
|
|
21183
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
21214
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
21215
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
21184
21216
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
21185
21217
|
}
|
|
21186
|
-
return
|
|
21218
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
21187
21219
|
}
|
|
21188
21220
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
21189
21221
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -21396,6 +21428,33 @@ var metadataLockSchema = object4({
|
|
|
21396
21428
|
nonce: string5().min(1)
|
|
21397
21429
|
}).strict();
|
|
21398
21430
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21431
|
+
var cachedRuntimeVersion = null;
|
|
21432
|
+
var runtimeVersion = () => {
|
|
21433
|
+
if (cachedRuntimeVersion === null) {
|
|
21434
|
+
try {
|
|
21435
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
21436
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
21437
|
+
} catch {
|
|
21438
|
+
cachedRuntimeVersion = "0.0.0";
|
|
21439
|
+
}
|
|
21440
|
+
}
|
|
21441
|
+
return cachedRuntimeVersion;
|
|
21442
|
+
};
|
|
21443
|
+
var stampNew = (record) => ({
|
|
21444
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
21445
|
+
updatedWith: runtimeVersion()
|
|
21446
|
+
});
|
|
21447
|
+
var isNewerVersion = (candidate, current) => {
|
|
21448
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
21449
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
21450
|
+
for (let index = 0;index < 3; index += 1) {
|
|
21451
|
+
const left = next[index] ?? 0;
|
|
21452
|
+
const right = now[index] ?? 0;
|
|
21453
|
+
if (left !== right)
|
|
21454
|
+
return left > right;
|
|
21455
|
+
}
|
|
21456
|
+
return false;
|
|
21457
|
+
};
|
|
21399
21458
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
21400
21459
|
`;
|
|
21401
21460
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -21422,12 +21481,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
21422
21481
|
class TaskStore {
|
|
21423
21482
|
root;
|
|
21424
21483
|
constructor(root) {
|
|
21425
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
21484
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
21426
21485
|
}
|
|
21427
21486
|
readTask(taskId) {
|
|
21428
21487
|
if (!validId(taskId))
|
|
21429
21488
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
21430
|
-
const result = this.readRecord(
|
|
21489
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
21431
21490
|
if (!result.exists)
|
|
21432
21491
|
return failure("not_found", "task not found", { taskId });
|
|
21433
21492
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -21461,7 +21520,7 @@ class TaskStore {
|
|
|
21461
21520
|
});
|
|
21462
21521
|
const tasks = [];
|
|
21463
21522
|
for (const name of names.sort()) {
|
|
21464
|
-
const item = this.readRecord(
|
|
21523
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
21465
21524
|
if (!item.exists)
|
|
21466
21525
|
continue;
|
|
21467
21526
|
if (!item.result.ok)
|
|
@@ -21495,7 +21554,7 @@ class TaskStore {
|
|
|
21495
21554
|
if (!current.ok)
|
|
21496
21555
|
return current;
|
|
21497
21556
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
21498
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21557
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21499
21558
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
21500
21559
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21501
21560
|
});
|
|
@@ -21507,11 +21566,17 @@ class TaskStore {
|
|
|
21507
21566
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
21508
21567
|
if (current.data && !previousWorkspaceBytes)
|
|
21509
21568
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
21510
|
-
const workspace = current.data ? {
|
|
21569
|
+
const workspace = current.data ? {
|
|
21570
|
+
...current.data,
|
|
21571
|
+
revision: newRevision(),
|
|
21572
|
+
root: this.root,
|
|
21573
|
+
runtime: stampNew(current.data)
|
|
21574
|
+
} : {
|
|
21511
21575
|
schemaVersion: SCHEMA_VERSION,
|
|
21512
21576
|
id: newId(),
|
|
21513
21577
|
revision: newRevision(),
|
|
21514
21578
|
root: this.root,
|
|
21579
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21515
21580
|
writer: null
|
|
21516
21581
|
};
|
|
21517
21582
|
const timestamp = value.now ?? now();
|
|
@@ -21533,6 +21598,7 @@ class TaskStore {
|
|
|
21533
21598
|
status: "active",
|
|
21534
21599
|
closure: null,
|
|
21535
21600
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
21601
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21536
21602
|
assessments: [],
|
|
21537
21603
|
policy: null,
|
|
21538
21604
|
policyChanges: [],
|
|
@@ -21563,7 +21629,7 @@ class TaskStore {
|
|
|
21563
21629
|
if (!current.ok)
|
|
21564
21630
|
return current;
|
|
21565
21631
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
21566
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21632
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21567
21633
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
21568
21634
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21569
21635
|
});
|
|
@@ -21571,11 +21637,17 @@ class TaskStore {
|
|
|
21571
21637
|
if (current.data && !previousWorkspaceBytes)
|
|
21572
21638
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
21573
21639
|
const timestamp = input.now ?? now();
|
|
21574
|
-
const workspace = current.data ? {
|
|
21640
|
+
const workspace = current.data ? {
|
|
21641
|
+
...current.data,
|
|
21642
|
+
revision: newRevision(),
|
|
21643
|
+
root: this.root,
|
|
21644
|
+
runtime: stampNew(current.data)
|
|
21645
|
+
} : {
|
|
21575
21646
|
schemaVersion: SCHEMA_VERSION,
|
|
21576
21647
|
id: input.workspaceId ?? newId(),
|
|
21577
21648
|
revision: newRevision(),
|
|
21578
21649
|
root: this.root,
|
|
21650
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21579
21651
|
writer: null
|
|
21580
21652
|
};
|
|
21581
21653
|
const task = {
|
|
@@ -21583,7 +21655,11 @@ class TaskStore {
|
|
|
21583
21655
|
workspaceId: workspace.id,
|
|
21584
21656
|
revision: newRevision(),
|
|
21585
21657
|
createdAt: timestamp,
|
|
21586
|
-
updatedAt: timestamp
|
|
21658
|
+
updatedAt: timestamp,
|
|
21659
|
+
runtime: {
|
|
21660
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
21661
|
+
updatedWith: runtimeVersion()
|
|
21662
|
+
}
|
|
21587
21663
|
};
|
|
21588
21664
|
const validTask = taskRecordSchema.safeParse(task);
|
|
21589
21665
|
if (!validTask.success)
|
|
@@ -21632,7 +21708,8 @@ class TaskStore {
|
|
|
21632
21708
|
workspaceId: current.data.workspaceId,
|
|
21633
21709
|
createdAt: current.data.createdAt,
|
|
21634
21710
|
revision: context.revision,
|
|
21635
|
-
updatedAt: context.now
|
|
21711
|
+
updatedAt: context.now,
|
|
21712
|
+
runtime: stampNew(current.data)
|
|
21636
21713
|
};
|
|
21637
21714
|
const valid = taskRecordSchema.safeParse(record);
|
|
21638
21715
|
if (!valid.success)
|
|
@@ -21666,7 +21743,8 @@ class TaskStore {
|
|
|
21666
21743
|
...changed.data,
|
|
21667
21744
|
id: current.data.id,
|
|
21668
21745
|
root: this.root,
|
|
21669
|
-
revision: context.revision
|
|
21746
|
+
revision: context.revision,
|
|
21747
|
+
runtime: stampNew(current.data)
|
|
21670
21748
|
};
|
|
21671
21749
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
21672
21750
|
if (!valid.success)
|
|
@@ -21709,7 +21787,8 @@ class TaskStore {
|
|
|
21709
21787
|
...changedWorkspace.data,
|
|
21710
21788
|
id: workspace.data.id,
|
|
21711
21789
|
root: this.root,
|
|
21712
|
-
revision: workspaceContext.revision
|
|
21790
|
+
revision: workspaceContext.revision,
|
|
21791
|
+
runtime: stampNew(workspace.data)
|
|
21713
21792
|
});
|
|
21714
21793
|
if (!nextWorkspace.success)
|
|
21715
21794
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -21734,7 +21813,8 @@ class TaskStore {
|
|
|
21734
21813
|
workspaceId: task.data.workspaceId,
|
|
21735
21814
|
createdAt: task.data.createdAt,
|
|
21736
21815
|
revision: taskContext.revision,
|
|
21737
|
-
updatedAt: taskContext.now
|
|
21816
|
+
updatedAt: taskContext.now,
|
|
21817
|
+
runtime: stampNew(task.data)
|
|
21738
21818
|
});
|
|
21739
21819
|
if (!nextTask.success) {
|
|
21740
21820
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -21761,7 +21841,7 @@ class TaskStore {
|
|
|
21761
21841
|
if (match)
|
|
21762
21842
|
candidates.push({
|
|
21763
21843
|
target: match[1],
|
|
21764
|
-
path:
|
|
21844
|
+
path: path4.join(this.recoveryDir, name),
|
|
21765
21845
|
digest: match[3]
|
|
21766
21846
|
});
|
|
21767
21847
|
}
|
|
@@ -21954,7 +22034,8 @@ class TaskStore {
|
|
|
21954
22034
|
const value = {
|
|
21955
22035
|
...workspace,
|
|
21956
22036
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
21957
|
-
revision: newRevision()
|
|
22037
|
+
revision: newRevision(),
|
|
22038
|
+
runtime: stampNew(workspace)
|
|
21958
22039
|
};
|
|
21959
22040
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
21960
22041
|
}
|
|
@@ -22032,7 +22113,7 @@ class TaskStore {
|
|
|
22032
22113
|
}
|
|
22033
22114
|
fs4.renameSync(temporary, file);
|
|
22034
22115
|
temporary = undefined;
|
|
22035
|
-
this.fsyncDirectory(
|
|
22116
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
22036
22117
|
return success(null, null, value);
|
|
22037
22118
|
} catch (error) {
|
|
22038
22119
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -22046,9 +22127,9 @@ class TaskStore {
|
|
|
22046
22127
|
}
|
|
22047
22128
|
}
|
|
22048
22129
|
saveRecovery(file, bytes) {
|
|
22049
|
-
const target =
|
|
22050
|
-
const id = target === "task" ?
|
|
22051
|
-
const destination =
|
|
22130
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
22131
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
22132
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
22052
22133
|
if (fs4.existsSync(destination)) {
|
|
22053
22134
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
22054
22135
|
return;
|
|
@@ -22126,7 +22207,7 @@ class TaskStore {
|
|
|
22126
22207
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
22127
22208
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
22128
22209
|
return null;
|
|
22129
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
22210
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
22130
22211
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
22131
22212
|
return null;
|
|
22132
22213
|
ids.add(parsed.data.workspaceId);
|
|
@@ -22168,7 +22249,12 @@ class TaskStore {
|
|
|
22168
22249
|
if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
22169
22250
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
22170
22251
|
const parsed = schema.safeParse(value);
|
|
22171
|
-
|
|
22252
|
+
if (parsed.success)
|
|
22253
|
+
return success(null, null, parsed.data);
|
|
22254
|
+
const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
|
|
22255
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
22256
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
22257
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
22172
22258
|
}
|
|
22173
22259
|
processStart(pid) {
|
|
22174
22260
|
try {
|
|
@@ -22178,43 +22264,98 @@ class TaskStore {
|
|
|
22178
22264
|
}
|
|
22179
22265
|
}
|
|
22180
22266
|
conflict(expected, actual) {
|
|
22181
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
22267
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
22182
22268
|
expectedRevision: expected,
|
|
22183
22269
|
actualRevision: actual
|
|
22184
22270
|
});
|
|
22185
22271
|
}
|
|
22186
22272
|
taskPath(taskId) {
|
|
22187
|
-
return
|
|
22273
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
22188
22274
|
}
|
|
22189
22275
|
get workitDir() {
|
|
22190
|
-
return
|
|
22276
|
+
return path4.join(this.root, ".workit");
|
|
22191
22277
|
}
|
|
22192
22278
|
get tasksDir() {
|
|
22193
|
-
return
|
|
22279
|
+
return path4.join(this.workitDir, "tasks");
|
|
22194
22280
|
}
|
|
22195
22281
|
get recoveryDir() {
|
|
22196
|
-
return
|
|
22282
|
+
return path4.join(this.workitDir, "recovery");
|
|
22197
22283
|
}
|
|
22198
22284
|
get workspacePath() {
|
|
22199
|
-
return
|
|
22285
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
22200
22286
|
}
|
|
22201
22287
|
get lockPath() {
|
|
22202
|
-
return
|
|
22288
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
22203
22289
|
}
|
|
22204
22290
|
get gitignorePath() {
|
|
22205
|
-
return
|
|
22291
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
22206
22292
|
}
|
|
22207
22293
|
}
|
|
22294
|
+
// packages/workit-core/src/core/workers.ts
|
|
22295
|
+
var same = (left, right) => {
|
|
22296
|
+
try {
|
|
22297
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
22298
|
+
} catch {
|
|
22299
|
+
return false;
|
|
22300
|
+
}
|
|
22301
|
+
};
|
|
22302
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
22303
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
22304
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
22305
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
22306
|
+
const code = char.charCodeAt(0);
|
|
22307
|
+
return code < 32 || code === 127;
|
|
22308
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
22309
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
22310
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
22311
|
+
function assertProductWriteAllowed(input) {
|
|
22312
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
22313
|
+
return failure("invalid_input", "invalid product write path");
|
|
22314
|
+
if (!input.store)
|
|
22315
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
22316
|
+
const task = input.store.readTask(input.task.id);
|
|
22317
|
+
if (!task.ok)
|
|
22318
|
+
return task;
|
|
22319
|
+
const workspace = input.store.readWorkspace();
|
|
22320
|
+
if (!workspace.ok)
|
|
22321
|
+
return workspace;
|
|
22322
|
+
if (!workspace.data)
|
|
22323
|
+
return failure("not_found", "workspace not found");
|
|
22324
|
+
const workspaceRecord = workspace.data;
|
|
22325
|
+
if (task.data.status !== "active")
|
|
22326
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
22327
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
22328
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
22329
|
+
const writer = workspaceRecord.writer;
|
|
22330
|
+
if (!writer)
|
|
22331
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
22332
|
+
if (writer.state === "uncertain")
|
|
22333
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
22334
|
+
owner: writer.owner
|
|
22335
|
+
});
|
|
22336
|
+
const caller = callerValue(input.caller);
|
|
22337
|
+
const workerId = workerIdOf(input.caller);
|
|
22338
|
+
const owner = writer.owner;
|
|
22339
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
22340
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
22341
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
22342
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
22343
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
22344
|
+
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))))
|
|
22345
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
22346
|
+
return success(null, workspaceRecord.revision, owner);
|
|
22347
|
+
}
|
|
22348
|
+
|
|
22208
22349
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22209
22350
|
import { createHash as createHash4 } from "node:crypto";
|
|
22210
22351
|
import * as fs6 from "node:fs";
|
|
22211
22352
|
import { spawnSync } from "node:child_process";
|
|
22212
|
-
import
|
|
22353
|
+
import path6 from "node:path";
|
|
22213
22354
|
|
|
22214
22355
|
// packages/workit-core/src/core/authority.ts
|
|
22215
22356
|
import { createHash as createHash3 } from "node:crypto";
|
|
22216
22357
|
import * as fs5 from "node:fs";
|
|
22217
|
-
import
|
|
22358
|
+
import path5 from "node:path";
|
|
22218
22359
|
var verifiedAuthorities = new WeakMap;
|
|
22219
22360
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
22220
22361
|
const token = {};
|
|
@@ -22341,20 +22482,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
22341
22482
|
continue;
|
|
22342
22483
|
if (!reference.digest)
|
|
22343
22484
|
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"
|
|
22485
|
+
const target = path5.resolve(root, reference.path);
|
|
22486
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
22487
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22488
|
+
fields: [
|
|
22489
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
22490
|
+
]
|
|
22491
|
+
});
|
|
22347
22492
|
try {
|
|
22348
|
-
const relative =
|
|
22493
|
+
const relative = path5.relative(root, target);
|
|
22349
22494
|
let current = root;
|
|
22350
|
-
for (const segment of relative.split(
|
|
22495
|
+
for (const segment of relative.split(path5.sep)) {
|
|
22351
22496
|
if (!segment)
|
|
22352
22497
|
continue;
|
|
22353
|
-
current =
|
|
22498
|
+
current = path5.join(current, segment);
|
|
22354
22499
|
const stat = fs5.lstatSync(current);
|
|
22355
22500
|
const real = fs5.realpathSync(current);
|
|
22356
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
22357
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
22501
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
22502
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22503
|
+
fields: [
|
|
22504
|
+
{
|
|
22505
|
+
path: "contentRefs.path",
|
|
22506
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
22507
|
+
}
|
|
22508
|
+
]
|
|
22509
|
+
});
|
|
22358
22510
|
if (stat.isSymbolicLink())
|
|
22359
22511
|
return failure("invalid_input", "document reference uses a symlink");
|
|
22360
22512
|
if (current === target && !stat.isFile())
|
|
@@ -22701,14 +22853,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
22701
22853
|
|
|
22702
22854
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22703
22855
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
22704
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
22705
|
-
var relative = (root, candidate) =>
|
|
22856
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
22857
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
22706
22858
|
var canonicalPath = (value) => {
|
|
22707
22859
|
if (typeof value !== "string" || !value)
|
|
22708
22860
|
return null;
|
|
22709
22861
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
22710
22862
|
return null;
|
|
22711
|
-
const normalized =
|
|
22863
|
+
const normalized = path6.posix.normalize(value);
|
|
22712
22864
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
22713
22865
|
return null;
|
|
22714
22866
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -22756,14 +22908,14 @@ var gitPaths = (root) => {
|
|
|
22756
22908
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
22757
22909
|
};
|
|
22758
22910
|
}
|
|
22759
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22911
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
22760
22912
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
22761
22913
|
if (stagedDeleted.status !== 0) {
|
|
22762
22914
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
22763
22915
|
if (!/not a git repository/i.test(stderr))
|
|
22764
22916
|
return { paths, uncertain: true, git: true };
|
|
22765
22917
|
} else if (stagedDeleted.stdout) {
|
|
22766
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22918
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
22767
22919
|
}
|
|
22768
22920
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
22769
22921
|
};
|
|
@@ -22778,7 +22930,7 @@ var walk = (root, directory, output) => {
|
|
|
22778
22930
|
for (const name of names) {
|
|
22779
22931
|
if (name === ".git" || name === ".workit")
|
|
22780
22932
|
continue;
|
|
22781
|
-
const target =
|
|
22933
|
+
const target = path6.join(directory, name);
|
|
22782
22934
|
let stat;
|
|
22783
22935
|
try {
|
|
22784
22936
|
stat = fs6.lstatSync(target);
|
|
@@ -22796,17 +22948,17 @@ var walk = (root, directory, output) => {
|
|
|
22796
22948
|
var scopeRoots = (root, scope) => {
|
|
22797
22949
|
const roots = [];
|
|
22798
22950
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
22799
|
-
const target =
|
|
22951
|
+
const target = path6.resolve(root, item);
|
|
22800
22952
|
if (!inside(root, target))
|
|
22801
|
-
return failure("invalid_input",
|
|
22953
|
+
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
22954
|
let ancestor = target;
|
|
22803
22955
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
22804
|
-
ancestor =
|
|
22956
|
+
ancestor = path6.dirname(ancestor);
|
|
22805
22957
|
try {
|
|
22806
22958
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
22807
|
-
return failure("invalid_input",
|
|
22959
|
+
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
22960
|
} catch {
|
|
22809
|
-
return failure("invalid_input",
|
|
22961
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
22810
22962
|
}
|
|
22811
22963
|
roots.push(target);
|
|
22812
22964
|
}
|
|
@@ -22821,7 +22973,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22821
22973
|
}
|
|
22822
22974
|
const normalizedScope = canonicalScope(scope);
|
|
22823
22975
|
if (!normalizedScope)
|
|
22824
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
22976
|
+
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
22977
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
22826
22978
|
if (!roots.ok)
|
|
22827
22979
|
return roots;
|
|
@@ -22853,7 +23005,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22853
23005
|
for (const item of [...names].sort()) {
|
|
22854
23006
|
if (!scopeMatches(item, normalizedScope))
|
|
22855
23007
|
continue;
|
|
22856
|
-
const target =
|
|
23008
|
+
const target = path6.join(checkout, item);
|
|
22857
23009
|
try {
|
|
22858
23010
|
const stat = fs6.lstatSync(target);
|
|
22859
23011
|
if (stat.isSymbolicLink()) {
|
|
@@ -22975,7 +23127,7 @@ function evaluateEvidence(task, candidate) {
|
|
|
22975
23127
|
}
|
|
22976
23128
|
var scopeCovers2 = scopeCovers;
|
|
22977
23129
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
22978
|
-
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
23130
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
22979
23131
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
22980
23132
|
if (kind !== "passed")
|
|
22981
23133
|
return false;
|
|
@@ -23010,7 +23162,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
23010
23162
|
return true;
|
|
23011
23163
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
23012
23164
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
23013
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
23165
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
23014
23166
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
23015
23167
|
});
|
|
23016
23168
|
if (passed.length) {
|
|
@@ -23051,7 +23203,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
23051
23203
|
status: "satisfied",
|
|
23052
23204
|
evidenceIds: [],
|
|
23053
23205
|
decisionIds: decisions.map((decision) => decision.id),
|
|
23054
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
23206
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
23055
23207
|
};
|
|
23056
23208
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
23057
23209
|
if (limitations.length)
|
|
@@ -23106,7 +23258,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
23106
23258
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
23107
23259
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
23108
23260
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
23109
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
23261
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
23262
|
+
requirements: blocking.map((item) => {
|
|
23263
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
23264
|
+
return {
|
|
23265
|
+
requirementId: item.requirementId,
|
|
23266
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
23267
|
+
reason: item.reason,
|
|
23268
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
23269
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
23270
|
+
};
|
|
23271
|
+
})
|
|
23110
23272
|
});
|
|
23111
23273
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
23112
23274
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -23142,7 +23304,7 @@ function reconcileResume(view, observations = []) {
|
|
|
23142
23304
|
return candidate;
|
|
23143
23305
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
23144
23306
|
const blockers = [...view.task.progress.blockers];
|
|
23145
|
-
if (view.task.workers.some((entry) =>
|
|
23307
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
23146
23308
|
blockers.push({
|
|
23147
23309
|
reason: "worker state requires reconciliation",
|
|
23148
23310
|
dependentAction: "resume",
|
|
@@ -23175,7 +23337,11 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
23175
23337
|
}).strict(),
|
|
23176
23338
|
object4({
|
|
23177
23339
|
operation: literal3("git.commit"),
|
|
23178
|
-
payload: object4({
|
|
23340
|
+
payload: object4({
|
|
23341
|
+
message: string5().min(1).optional(),
|
|
23342
|
+
plan_steps: array3(string5().min(1)).optional(),
|
|
23343
|
+
plan_branch: string5().min(1).optional()
|
|
23344
|
+
}).strict()
|
|
23179
23345
|
}).strict(),
|
|
23180
23346
|
object4({
|
|
23181
23347
|
operation: literal3("git.push"),
|
|
@@ -23410,7 +23576,7 @@ function resolveRequirements(input) {
|
|
|
23410
23576
|
].join(", ")}`,
|
|
23411
23577
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
23412
23578
|
before: "dependent_action",
|
|
23413
|
-
dependentAction:
|
|
23579
|
+
dependentAction: null,
|
|
23414
23580
|
acceptanceAllowed: false
|
|
23415
23581
|
}));
|
|
23416
23582
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -23421,7 +23587,7 @@ function resolveRequirements(input) {
|
|
|
23421
23587
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
23422
23588
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
23423
23589
|
before: "dependent_action",
|
|
23424
|
-
dependentAction:
|
|
23590
|
+
dependentAction: null,
|
|
23425
23591
|
acceptanceAllowed: false
|
|
23426
23592
|
}));
|
|
23427
23593
|
if (signals.behaviorChange.value === true)
|
|
@@ -23579,59 +23745,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
23579
23745
|
return parsed.data;
|
|
23580
23746
|
}
|
|
23581
23747
|
|
|
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
23748
|
// packages/workit-core/src/core/task-engine.ts
|
|
23636
23749
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
23637
23750
|
kind,
|
|
@@ -23762,15 +23875,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
23762
23875
|
return failure("invalid_transition", "worker has not started");
|
|
23763
23876
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
23764
23877
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
23878
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
23765
23879
|
const nextEntry = {
|
|
23766
23880
|
...entry,
|
|
23767
23881
|
recordedAt: input.now ?? entry.recordedAt,
|
|
23768
23882
|
provenance: authority.provenance,
|
|
23769
|
-
data: {
|
|
23883
|
+
data: {
|
|
23884
|
+
...entry.data,
|
|
23885
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
23886
|
+
session: input.session
|
|
23887
|
+
}
|
|
23770
23888
|
};
|
|
23771
23889
|
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
23890
|
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 ===
|
|
23891
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
23774
23892
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
23775
23893
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
23776
23894
|
taskId: task.data.id,
|
|
@@ -24140,7 +24258,7 @@ class WorkitCore {
|
|
|
24140
24258
|
activeWorkerBlocker(task, workspace) {
|
|
24141
24259
|
if (workspace.writer)
|
|
24142
24260
|
return failure("recovery_required", "writer ownership must be released first");
|
|
24143
|
-
if (task.workers.some((entry) =>
|
|
24261
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
24144
24262
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
24145
24263
|
return null;
|
|
24146
24264
|
}
|
|
@@ -24377,11 +24495,6 @@ class WorkitCore {
|
|
|
24377
24495
|
if (helper && !helper.ok)
|
|
24378
24496
|
return helper;
|
|
24379
24497
|
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
24498
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
24386
24499
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
24387
24500
|
if (evidence.kind === "review") {
|
|
@@ -24398,6 +24511,11 @@ class WorkitCore {
|
|
|
24398
24511
|
evidence.beforeCandidateId ??= null;
|
|
24399
24512
|
evidence.candidateId ??= null;
|
|
24400
24513
|
}
|
|
24514
|
+
if (helper?.ok) {
|
|
24515
|
+
const assignment = helper.data.data.assignment;
|
|
24516
|
+
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))
|
|
24517
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
24518
|
+
}
|
|
24401
24519
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
24402
24520
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
24403
24521
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -24451,7 +24569,12 @@ class WorkitCore {
|
|
|
24451
24569
|
const input = parsed.data;
|
|
24452
24570
|
if (nativeRequired && input.action !== "record")
|
|
24453
24571
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
24454
|
-
|
|
24572
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
24573
|
+
if (stated && input.purpose === "action")
|
|
24574
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
24575
|
+
if (stated && !input.binding?.statedChoice)
|
|
24576
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
24577
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
24455
24578
|
return failure("permission_denied", "native decision observation is required");
|
|
24456
24579
|
const task = this.store.readTask(input.taskId);
|
|
24457
24580
|
if (!task.ok)
|
|
@@ -24484,7 +24607,7 @@ class WorkitCore {
|
|
|
24484
24607
|
consumption: null
|
|
24485
24608
|
};
|
|
24486
24609
|
const data = { ...base, digest: decisionDigest(base) };
|
|
24487
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
24610
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
24488
24611
|
observation: nativeObservation,
|
|
24489
24612
|
expected: {
|
|
24490
24613
|
taskId: task.data.id,
|
|
@@ -25293,6 +25416,9 @@ class WorkitCore {
|
|
|
25293
25416
|
id: task.id,
|
|
25294
25417
|
revision: task.revision,
|
|
25295
25418
|
workspaceRevision: workspace.data.revision,
|
|
25419
|
+
createdAt: task.createdAt,
|
|
25420
|
+
updatedAt: task.updatedAt,
|
|
25421
|
+
runtime: task.runtime ?? null,
|
|
25296
25422
|
objective: task.intent.data.objective,
|
|
25297
25423
|
status: task.status,
|
|
25298
25424
|
closure: task.closure,
|
|
@@ -25378,7 +25504,7 @@ class WorkitCore {
|
|
|
25378
25504
|
const base = reconcileResume(effectiveView);
|
|
25379
25505
|
if (!base.ok)
|
|
25380
25506
|
return base;
|
|
25381
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
25507
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
25382
25508
|
const blockers = [...base.data.blockers];
|
|
25383
25509
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
25384
25510
|
blockers.push({
|
|
@@ -25445,7 +25571,8 @@ class WorkitCore {
|
|
|
25445
25571
|
...current,
|
|
25446
25572
|
status,
|
|
25447
25573
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
25448
|
-
progress:
|
|
25574
|
+
progress: current.progress,
|
|
25575
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
25449
25576
|
})
|
|
25450
25577
|
});
|
|
25451
25578
|
if (!changed.ok)
|
|
@@ -25481,7 +25608,7 @@ class WorkitCore {
|
|
|
25481
25608
|
return workspace;
|
|
25482
25609
|
if (!workspace.data)
|
|
25483
25610
|
return failure("not_found", "workspace not found");
|
|
25484
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
25611
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
25485
25612
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
25486
25613
|
this.fillRevisions(input, task.data, workspace.data);
|
|
25487
25614
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -25592,15 +25719,15 @@ function resolveGitRevision(root, value) {
|
|
|
25592
25719
|
}
|
|
25593
25720
|
function resolveInside(root, candidate) {
|
|
25594
25721
|
const base = realpathSync5(root);
|
|
25595
|
-
const target =
|
|
25596
|
-
if (target !== base && !target.startsWith(base +
|
|
25722
|
+
const target = path7.resolve(base, candidate);
|
|
25723
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
25597
25724
|
throw new Error("path must stay inside repository root");
|
|
25598
25725
|
}
|
|
25599
25726
|
let ancestor = target;
|
|
25600
|
-
while (!
|
|
25601
|
-
ancestor =
|
|
25727
|
+
while (!existsSync4(ancestor))
|
|
25728
|
+
ancestor = path7.dirname(ancestor);
|
|
25602
25729
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25603
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
25730
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
25604
25731
|
throw new Error("path must stay inside repository root");
|
|
25605
25732
|
}
|
|
25606
25733
|
return target;
|
|
@@ -25785,14 +25912,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25785
25912
|
import {
|
|
25786
25913
|
copyFileSync,
|
|
25787
25914
|
cpSync,
|
|
25788
|
-
existsSync as
|
|
25915
|
+
existsSync as existsSync5,
|
|
25789
25916
|
mkdirSync as mkdirSync2,
|
|
25790
25917
|
readFileSync as readFileSync4,
|
|
25791
25918
|
readdirSync as readdirSync3,
|
|
25792
25919
|
writeFileSync as writeFileSync2
|
|
25793
25920
|
} from "node:fs";
|
|
25794
25921
|
import os2 from "node:os";
|
|
25795
|
-
import
|
|
25922
|
+
import path8 from "node:path";
|
|
25796
25923
|
var diagnosticLogger;
|
|
25797
25924
|
var PRESETS = {
|
|
25798
25925
|
gitflow: {
|
|
@@ -25813,7 +25940,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25813
25940
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25814
25941
|
};
|
|
25815
25942
|
};
|
|
25816
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
25943
|
+
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
25944
|
var migratedDir = null;
|
|
25818
25945
|
var migrationFailed = false;
|
|
25819
25946
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25823,12 +25950,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25823
25950
|
migratedDir = dir;
|
|
25824
25951
|
return dir;
|
|
25825
25952
|
}
|
|
25826
|
-
const legacy =
|
|
25827
|
-
if (!
|
|
25953
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25954
|
+
if (!existsSync5(legacy)) {
|
|
25828
25955
|
migratedDir = dir;
|
|
25829
25956
|
return dir;
|
|
25830
25957
|
}
|
|
25831
|
-
if (!migrationFailed &&
|
|
25958
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25832
25959
|
migratedDir = dir;
|
|
25833
25960
|
return dir;
|
|
25834
25961
|
}
|
|
@@ -25836,9 +25963,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25836
25963
|
mkdirSync2(dir, { recursive: true });
|
|
25837
25964
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25838
25965
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25839
|
-
const src =
|
|
25840
|
-
const dest =
|
|
25841
|
-
if (
|
|
25966
|
+
const src = path8.join(legacy, entry.name);
|
|
25967
|
+
const dest = path8.join(dir, entry.name);
|
|
25968
|
+
if (existsSync5(dest))
|
|
25842
25969
|
continue;
|
|
25843
25970
|
try {
|
|
25844
25971
|
if (entry.isDirectory())
|
|
@@ -25911,7 +26038,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25911
26038
|
};
|
|
25912
26039
|
};
|
|
25913
26040
|
var readConfigTyped = (dir) => {
|
|
25914
|
-
const file =
|
|
26041
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25915
26042
|
return parseConfigResult(readSafe(file), file);
|
|
25916
26043
|
};
|
|
25917
26044
|
var readConfig = () => {
|
|
@@ -25947,9 +26074,9 @@ var COMMIT_PRESETS = [
|
|
|
25947
26074
|
|
|
25948
26075
|
// packages/workit-core/src/core/workspaces.ts
|
|
25949
26076
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25950
|
-
import
|
|
26077
|
+
import path9 from "node:path";
|
|
25951
26078
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25952
|
-
const file =
|
|
26079
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25953
26080
|
let raw;
|
|
25954
26081
|
try {
|
|
25955
26082
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26047,10 +26174,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26047
26174
|
|
|
26048
26175
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26049
26176
|
import fs7 from "node:fs";
|
|
26050
|
-
import
|
|
26177
|
+
import path10 from "node:path";
|
|
26051
26178
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26052
26179
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26053
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26180
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26054
26181
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26055
26182
|
var remoteProvider = (cwd) => {
|
|
26056
26183
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26133,8 +26260,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26133
26260
|
}
|
|
26134
26261
|
const prov = cfg[provider] ?? {};
|
|
26135
26262
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26136
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26137
|
-
const tokenPath =
|
|
26263
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26264
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26138
26265
|
let tokenOk = false;
|
|
26139
26266
|
if (fs7.existsSync(tokenPath)) {
|
|
26140
26267
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26143,7 +26270,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26143
26270
|
}
|
|
26144
26271
|
const out = {
|
|
26145
26272
|
ok: true,
|
|
26146
|
-
configPath:
|
|
26273
|
+
configPath: path10.resolve(cfgPath),
|
|
26147
26274
|
provider,
|
|
26148
26275
|
defaultTargetBranch: defaultTarget,
|
|
26149
26276
|
pr: cfg.pr ?? {},
|
|
@@ -26236,18 +26363,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26236
26363
|
|
|
26237
26364
|
// packages/workit-core/src/core/pr-create.ts
|
|
26238
26365
|
import fs8 from "node:fs";
|
|
26239
|
-
import
|
|
26366
|
+
import path11 from "node:path";
|
|
26240
26367
|
function parseGhIssue(value) {
|
|
26241
26368
|
const m = /issues\/(\d+)/.exec(value);
|
|
26242
26369
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26243
26370
|
}
|
|
26244
26371
|
function whichOnPath(tool) {
|
|
26245
26372
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26246
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26373
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26247
26374
|
if (!dir)
|
|
26248
26375
|
continue;
|
|
26249
26376
|
for (const name of names) {
|
|
26250
|
-
const candidate =
|
|
26377
|
+
const candidate = path11.join(dir, name);
|
|
26251
26378
|
try {
|
|
26252
26379
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26253
26380
|
return candidate;
|
|
@@ -26260,8 +26387,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26260
26387
|
|
|
26261
26388
|
// packages/workit-core/src/core/repo-context.ts
|
|
26262
26389
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26263
|
-
import { existsSync as
|
|
26264
|
-
import
|
|
26390
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26391
|
+
import path12 from "node:path";
|
|
26265
26392
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26266
26393
|
try {
|
|
26267
26394
|
gitRevisionParts(value);
|
|
@@ -26395,7 +26522,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26395
26522
|
]) {
|
|
26396
26523
|
const parts = rel.split("/");
|
|
26397
26524
|
const base = parts[parts.length - 1];
|
|
26398
|
-
const dir = parts.length > 1 ?
|
|
26525
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26399
26526
|
const actual = probeCaseInsensitive(dir, base);
|
|
26400
26527
|
if (actual)
|
|
26401
26528
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26426,8 +26553,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26426
26553
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26427
26554
|
};
|
|
26428
26555
|
var packageScripts = (cwd, keys) => {
|
|
26429
|
-
const pkgPath =
|
|
26430
|
-
if (!
|
|
26556
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26557
|
+
if (!existsSync6(pkgPath))
|
|
26431
26558
|
return [];
|
|
26432
26559
|
try {
|
|
26433
26560
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26452,9 +26579,9 @@ var documentationFiles = (cwd) => {
|
|
|
26452
26579
|
if (entry.isDirectory()) {
|
|
26453
26580
|
if (excluded.has(entry.name))
|
|
26454
26581
|
continue;
|
|
26455
|
-
walk(
|
|
26582
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26456
26583
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26457
|
-
matches.push("./" +
|
|
26584
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26458
26585
|
}
|
|
26459
26586
|
}
|
|
26460
26587
|
};
|
|
@@ -26462,7 +26589,7 @@ var documentationFiles = (cwd) => {
|
|
|
26462
26589
|
return matches.sort().slice(0, 200);
|
|
26463
26590
|
};
|
|
26464
26591
|
var readTrimmed = (file, maxLines) => {
|
|
26465
|
-
if (!
|
|
26592
|
+
if (!existsSync6(file))
|
|
26466
26593
|
return "";
|
|
26467
26594
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26468
26595
|
`);
|
|
@@ -26470,7 +26597,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26470
26597
|
`);
|
|
26471
26598
|
};
|
|
26472
26599
|
function prReadyContext(root, range) {
|
|
26473
|
-
const cwd =
|
|
26600
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26474
26601
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26475
26602
|
return invalidRange(cwd);
|
|
26476
26603
|
let stdout = "";
|
|
@@ -26528,7 +26655,7 @@ ${status.stderr}` : "");
|
|
|
26528
26655
|
stdout += `template_path: ${template}
|
|
26529
26656
|
|
|
26530
26657
|
`;
|
|
26531
|
-
stdout += readTrimmed(
|
|
26658
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26532
26659
|
} else {
|
|
26533
26660
|
stdout += `template_path: none
|
|
26534
26661
|
|
|
@@ -26538,7 +26665,7 @@ ${status.stderr}` : "");
|
|
|
26538
26665
|
stdout += `
|
|
26539
26666
|
`;
|
|
26540
26667
|
stdout += printSection("Recent Validation Signals");
|
|
26541
|
-
if (
|
|
26668
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26542
26669
|
stdout += `package.json detected. Common scripts:
|
|
26543
26670
|
`;
|
|
26544
26671
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26547,7 +26674,7 @@ ${status.stderr}` : "");
|
|
|
26547
26674
|
`) + `
|
|
26548
26675
|
`;
|
|
26549
26676
|
}
|
|
26550
|
-
if (
|
|
26677
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26551
26678
|
stdout += `Rust project detected.
|
|
26552
26679
|
`;
|
|
26553
26680
|
}
|
|
@@ -26580,7 +26707,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26580
26707
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26581
26708
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26582
26709
|
function changelogContext(root, range) {
|
|
26583
|
-
const cwd =
|
|
26710
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26584
26711
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26585
26712
|
return invalidRange(cwd);
|
|
26586
26713
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26598,8 +26725,8 @@ function changelogContext(root, range) {
|
|
|
26598
26725
|
stdout += CHANGELOG_RULES + `
|
|
26599
26726
|
`;
|
|
26600
26727
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26601
|
-
const changelogPath =
|
|
26602
|
-
if (
|
|
26728
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26729
|
+
if (existsSync6(changelogPath)) {
|
|
26603
26730
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26604
26731
|
`;
|
|
26605
26732
|
} else {
|
|
@@ -26618,7 +26745,7 @@ function changelogContext(root, range) {
|
|
|
26618
26745
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26619
26746
|
}
|
|
26620
26747
|
function docsRefreshContext(root, range) {
|
|
26621
|
-
const cwd =
|
|
26748
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26622
26749
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26623
26750
|
return invalidRange(cwd);
|
|
26624
26751
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26640,8 +26767,8 @@ function docsRefreshContext(root, range) {
|
|
|
26640
26767
|
`) + `
|
|
26641
26768
|
`;
|
|
26642
26769
|
stdout += printSection("README Preview");
|
|
26643
|
-
const readme =
|
|
26644
|
-
if (
|
|
26770
|
+
const readme = path12.join(cwd, "README.md");
|
|
26771
|
+
if (existsSync6(readme)) {
|
|
26645
26772
|
stdout += readTrimmed(readme, 220) + `
|
|
26646
26773
|
`;
|
|
26647
26774
|
} else {
|
|
@@ -26649,8 +26776,8 @@ function docsRefreshContext(root, range) {
|
|
|
26649
26776
|
`;
|
|
26650
26777
|
}
|
|
26651
26778
|
stdout += printSection("Package Scripts");
|
|
26652
|
-
const pkgPath =
|
|
26653
|
-
if (
|
|
26779
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26780
|
+
if (existsSync6(pkgPath)) {
|
|
26654
26781
|
try {
|
|
26655
26782
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26656
26783
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26663,7 +26790,7 @@ function docsRefreshContext(root, range) {
|
|
|
26663
26790
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26664
26791
|
}
|
|
26665
26792
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26666
|
-
const cwd =
|
|
26793
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26667
26794
|
if (!rangeOrTag) {
|
|
26668
26795
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26669
26796
|
`, exitCode: 1, cwd };
|
|
@@ -26701,7 +26828,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26701
26828
|
`;
|
|
26702
26829
|
stdout += printSection("Existing Release Files");
|
|
26703
26830
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26704
|
-
if (
|
|
26831
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26705
26832
|
stdout += rel + `
|
|
26706
26833
|
`;
|
|
26707
26834
|
}
|
|
@@ -26892,25 +27019,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26892
27019
|
import fs10 from "node:fs";
|
|
26893
27020
|
import path13 from "node:path";
|
|
26894
27021
|
|
|
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
27022
|
// packages/workit-core/src/core/templates.ts
|
|
26915
27023
|
var repoRoot2 = assetRoot();
|
|
26916
27024
|
|
|
@@ -27414,6 +27522,32 @@ var toolInputSchema = (family) => {
|
|
|
27414
27522
|
const schema = boundedOperationJsonSchema(family);
|
|
27415
27523
|
return { type: "object", ...schema };
|
|
27416
27524
|
};
|
|
27525
|
+
var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
|
|
27526
|
+
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
27527
|
+
const action = branch.properties?.action?.const;
|
|
27528
|
+
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
27529
|
+
});
|
|
27530
|
+
var advertisedToolSchemas = (attested) => {
|
|
27531
|
+
if (attested)
|
|
27532
|
+
return OPERATION_FAMILIES.map((family) => ({
|
|
27533
|
+
name: `workit_${family}`,
|
|
27534
|
+
description: operationDescription(family),
|
|
27535
|
+
inputSchema: toolInputSchema(family)
|
|
27536
|
+
}));
|
|
27537
|
+
return OPERATION_FAMILIES.flatMap((family) => {
|
|
27538
|
+
const branches = readOnlyBranches(family);
|
|
27539
|
+
if (!branches.length)
|
|
27540
|
+
return [];
|
|
27541
|
+
const schema = boundedOperationJsonSchema(family);
|
|
27542
|
+
return [
|
|
27543
|
+
{
|
|
27544
|
+
name: `workit_${family}`,
|
|
27545
|
+
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27546
|
+
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
27547
|
+
}
|
|
27548
|
+
];
|
|
27549
|
+
});
|
|
27550
|
+
};
|
|
27417
27551
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27418
27552
|
if (result.ok)
|
|
27419
27553
|
return result;
|
|
@@ -27468,13 +27602,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27468
27602
|
].filter((file) => existsSync7(file)));
|
|
27469
27603
|
let staleWarned = false;
|
|
27470
27604
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27471
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27472
|
-
|
|
27473
|
-
|
|
27474
|
-
|
|
27475
|
-
|
|
27476
|
-
}
|
|
27477
|
-
|
|
27605
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27606
|
+
let attested = false;
|
|
27607
|
+
try {
|
|
27608
|
+
const context = await contextProvider.current();
|
|
27609
|
+
attested = context.callerAttested === true;
|
|
27610
|
+
} catch {
|
|
27611
|
+
attested = false;
|
|
27612
|
+
}
|
|
27613
|
+
return { tools: advertisedToolSchemas(attested) };
|
|
27614
|
+
});
|
|
27478
27615
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27479
27616
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27480
27617
|
uri: `workit://context/${kind}`,
|
|
@@ -27589,7 +27726,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27589
27726
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27590
27727
|
if (!parsed.ok)
|
|
27591
27728
|
return resultForClient(parsed, workspaceRoot);
|
|
27592
|
-
if (context.callerAttested
|
|
27729
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27593
27730
|
return resultForClient({
|
|
27594
27731
|
ok: false,
|
|
27595
27732
|
schemaVersion: 1,
|