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