@brainervirus/workit-codex 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +354 -216
- package/dist/workit-hook.js +279 -145
- package/package.json +3 -3
- package/skills/workit-plan/SKILL.md +14 -2
package/dist/launch-mcp.js
CHANGED
|
@@ -6626,8 +6626,8 @@ import path15 from "node:path";
|
|
|
6626
6626
|
|
|
6627
6627
|
// packages/workit-core/src/core.ts
|
|
6628
6628
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
6629
|
-
import { existsSync as
|
|
6630
|
-
import
|
|
6629
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
6630
|
+
import path7 from "node:path";
|
|
6631
6631
|
|
|
6632
6632
|
// packages/workit-core/src/core/task-contract.ts
|
|
6633
6633
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -14419,6 +14419,7 @@ var decisionSchema = object({
|
|
|
14419
14419
|
scope: scopeSchema,
|
|
14420
14420
|
presented: text,
|
|
14421
14421
|
approvedContent: text,
|
|
14422
|
+
displayed: text.optional(),
|
|
14422
14423
|
contentRefs: array(refSchema)
|
|
14423
14424
|
}).strict(),
|
|
14424
14425
|
digest,
|
|
@@ -14497,6 +14498,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
14497
14498
|
path: ["decisionId"]
|
|
14498
14499
|
});
|
|
14499
14500
|
});
|
|
14501
|
+
var runtimeSchema = object({
|
|
14502
|
+
createdWith: text.nullable(),
|
|
14503
|
+
updatedWith: text
|
|
14504
|
+
}).strict();
|
|
14500
14505
|
var taskRecordSchema = object({
|
|
14501
14506
|
schemaVersion: literal(1),
|
|
14502
14507
|
id,
|
|
@@ -14510,6 +14515,8 @@ var taskRecordSchema = object({
|
|
|
14510
14515
|
status: _enum(["active", "paused", "closed"]),
|
|
14511
14516
|
closure: closureSchema.nullable(),
|
|
14512
14517
|
progress: progressSchema,
|
|
14518
|
+
pauseReason: text.nullable().optional(),
|
|
14519
|
+
runtime: runtimeSchema.optional(),
|
|
14513
14520
|
assessments: array(entrySchema(assessmentSchema)),
|
|
14514
14521
|
policy: policySchema.nullable(),
|
|
14515
14522
|
policyChanges: array(policyChangeSchema),
|
|
@@ -14525,6 +14532,7 @@ var workspaceRecordSchema = object({
|
|
|
14525
14532
|
id,
|
|
14526
14533
|
revision,
|
|
14527
14534
|
root: nonEmpty,
|
|
14535
|
+
runtime: runtimeSchema.optional(),
|
|
14528
14536
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
14529
14537
|
}).strict();
|
|
14530
14538
|
var capabilitySchema = object({
|
|
@@ -14552,6 +14560,9 @@ var taskSummarySchema = object({
|
|
|
14552
14560
|
id,
|
|
14553
14561
|
revision,
|
|
14554
14562
|
workspaceRevision: revision,
|
|
14563
|
+
createdAt: utc,
|
|
14564
|
+
updatedAt: utc,
|
|
14565
|
+
runtime: runtimeSchema.nullable(),
|
|
14555
14566
|
objective: text,
|
|
14556
14567
|
status: _enum(["active", "paused", "closed"]),
|
|
14557
14568
|
closure: closureSchema.nullable(),
|
|
@@ -14622,7 +14633,7 @@ var taskOperations = {
|
|
|
14622
14633
|
expectedWorkspaceRevision: revision.optional(),
|
|
14623
14634
|
outcome: outcomeSchema,
|
|
14624
14635
|
summary: text,
|
|
14625
|
-
decisionIds: array(id)
|
|
14636
|
+
decisionIds: array(id).optional()
|
|
14626
14637
|
})
|
|
14627
14638
|
};
|
|
14628
14639
|
var policyOperations = {
|
|
@@ -14714,14 +14725,15 @@ var writerOperations = {
|
|
|
14714
14725
|
...taskId,
|
|
14715
14726
|
expectedRevision: revision.optional(),
|
|
14716
14727
|
expectedWorkspaceRevision: revision.optional(),
|
|
14717
|
-
workerId: nullableId.optional()
|
|
14728
|
+
workerId: nullableId.optional(),
|
|
14729
|
+
reason: text.optional()
|
|
14718
14730
|
}),
|
|
14719
14731
|
release: operation({
|
|
14720
14732
|
action: literal("release"),
|
|
14721
14733
|
...taskId,
|
|
14722
14734
|
expectedRevision: revision.optional(),
|
|
14723
14735
|
expectedWorkspaceRevision: revision.optional(),
|
|
14724
|
-
reason: text
|
|
14736
|
+
reason: text.optional()
|
|
14725
14737
|
})
|
|
14726
14738
|
};
|
|
14727
14739
|
var stateOperations = {
|
|
@@ -14928,7 +14940,26 @@ function requirementId(input) {
|
|
|
14928
14940
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
14929
14941
|
import * as fs4 from "node:fs";
|
|
14930
14942
|
import { hostname } from "node:os";
|
|
14931
|
-
import
|
|
14943
|
+
import path4 from "node:path";
|
|
14944
|
+
|
|
14945
|
+
// packages/workit-core/src/core/package-root.ts
|
|
14946
|
+
import { existsSync } from "node:fs";
|
|
14947
|
+
import path from "node:path";
|
|
14948
|
+
import { fileURLToPath } from "node:url";
|
|
14949
|
+
var packageRoot = () => {
|
|
14950
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
14951
|
+
while (true) {
|
|
14952
|
+
const parent = path.dirname(dir);
|
|
14953
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
14954
|
+
return dir;
|
|
14955
|
+
dir = parent;
|
|
14956
|
+
}
|
|
14957
|
+
};
|
|
14958
|
+
var assetRoot = () => {
|
|
14959
|
+
const root = packageRoot();
|
|
14960
|
+
const assets = path.join(root, "assets");
|
|
14961
|
+
return existsSync(assets) ? assets : root;
|
|
14962
|
+
};
|
|
14932
14963
|
|
|
14933
14964
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
14934
14965
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -14979,7 +15010,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
14979
15010
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
14980
15011
|
import { randomBytes } from "node:crypto";
|
|
14981
15012
|
import fsSync from "node:fs";
|
|
14982
|
-
import
|
|
15013
|
+
import path2 from "node:path";
|
|
14983
15014
|
|
|
14984
15015
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
14985
15016
|
import fs from "node:fs";
|
|
@@ -15069,13 +15100,13 @@ ${ownershipToken}
|
|
|
15069
15100
|
return { raw, ownershipToken };
|
|
15070
15101
|
}
|
|
15071
15102
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
15072
|
-
const resolved =
|
|
15073
|
-
const lexicalRelative =
|
|
15074
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
15075
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
15103
|
+
const resolved = path2.resolve(lockPath);
|
|
15104
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
15105
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
15106
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
15076
15107
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
15077
15108
|
}
|
|
15078
|
-
return relative.split(
|
|
15109
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
15079
15110
|
}
|
|
15080
15111
|
function parseSidecarLockPayload(raw, parser) {
|
|
15081
15112
|
if (parser) {
|
|
@@ -15222,7 +15253,7 @@ function getFsSafeLockConfig() {
|
|
|
15222
15253
|
|
|
15223
15254
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
15224
15255
|
import fs3 from "node:fs";
|
|
15225
|
-
import
|
|
15256
|
+
import path3 from "node:path";
|
|
15226
15257
|
|
|
15227
15258
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
15228
15259
|
function sleepSync(ms) {
|
|
@@ -15313,26 +15344,26 @@ function canonicalLockParentSync(parent) {
|
|
|
15313
15344
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
15314
15345
|
}
|
|
15315
15346
|
function normalizeTargetPath(targetPath) {
|
|
15316
|
-
const resolved =
|
|
15317
|
-
fs3.mkdirSync(
|
|
15347
|
+
const resolved = path3.resolve(targetPath);
|
|
15348
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
15318
15349
|
try {
|
|
15319
|
-
return
|
|
15350
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
15320
15351
|
} catch {
|
|
15321
15352
|
return resolved;
|
|
15322
15353
|
}
|
|
15323
15354
|
}
|
|
15324
15355
|
function boundedLockPath(lockPath, lockRoot) {
|
|
15325
|
-
const resolved =
|
|
15356
|
+
const resolved = path3.resolve(lockPath);
|
|
15326
15357
|
if (!lockRoot)
|
|
15327
15358
|
return resolved;
|
|
15328
15359
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
15329
|
-
const parent =
|
|
15360
|
+
const parent = path3.dirname(resolved);
|
|
15330
15361
|
const parentReal = canonicalLockParentSync(parent);
|
|
15331
|
-
const parentRelative =
|
|
15332
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
15362
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
15363
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
15333
15364
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
15334
15365
|
}
|
|
15335
|
-
return
|
|
15366
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
15336
15367
|
}
|
|
15337
15368
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
15338
15369
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -15545,6 +15576,33 @@ var metadataLockSchema = object({
|
|
|
15545
15576
|
nonce: string2().min(1)
|
|
15546
15577
|
}).strict();
|
|
15547
15578
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15579
|
+
var cachedRuntimeVersion = null;
|
|
15580
|
+
var runtimeVersion = () => {
|
|
15581
|
+
if (cachedRuntimeVersion === null) {
|
|
15582
|
+
try {
|
|
15583
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
15584
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
15585
|
+
} catch {
|
|
15586
|
+
cachedRuntimeVersion = "0.0.0";
|
|
15587
|
+
}
|
|
15588
|
+
}
|
|
15589
|
+
return cachedRuntimeVersion;
|
|
15590
|
+
};
|
|
15591
|
+
var stampNew = (record) => ({
|
|
15592
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
15593
|
+
updatedWith: runtimeVersion()
|
|
15594
|
+
});
|
|
15595
|
+
var isNewerVersion = (candidate, current) => {
|
|
15596
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
15597
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
15598
|
+
for (let index = 0;index < 3; index += 1) {
|
|
15599
|
+
const left = next[index] ?? 0;
|
|
15600
|
+
const right = now[index] ?? 0;
|
|
15601
|
+
if (left !== right)
|
|
15602
|
+
return left > right;
|
|
15603
|
+
}
|
|
15604
|
+
return false;
|
|
15605
|
+
};
|
|
15548
15606
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
15549
15607
|
`;
|
|
15550
15608
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -15571,12 +15629,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
15571
15629
|
class TaskStore {
|
|
15572
15630
|
root;
|
|
15573
15631
|
constructor(root) {
|
|
15574
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
15632
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
15575
15633
|
}
|
|
15576
15634
|
readTask(taskId) {
|
|
15577
15635
|
if (!validId(taskId))
|
|
15578
15636
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
15579
|
-
const result = this.readRecord(
|
|
15637
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
15580
15638
|
if (!result.exists)
|
|
15581
15639
|
return failure("not_found", "task not found", { taskId });
|
|
15582
15640
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -15610,7 +15668,7 @@ class TaskStore {
|
|
|
15610
15668
|
});
|
|
15611
15669
|
const tasks = [];
|
|
15612
15670
|
for (const name of names.sort()) {
|
|
15613
|
-
const item = this.readRecord(
|
|
15671
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
15614
15672
|
if (!item.exists)
|
|
15615
15673
|
continue;
|
|
15616
15674
|
if (!item.result.ok)
|
|
@@ -15644,7 +15702,7 @@ class TaskStore {
|
|
|
15644
15702
|
if (!current.ok)
|
|
15645
15703
|
return current;
|
|
15646
15704
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
15647
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
15705
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15648
15706
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
15649
15707
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
15650
15708
|
});
|
|
@@ -15656,11 +15714,17 @@ class TaskStore {
|
|
|
15656
15714
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
15657
15715
|
if (current.data && !previousWorkspaceBytes)
|
|
15658
15716
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
15659
|
-
const workspace = current.data ? {
|
|
15717
|
+
const workspace = current.data ? {
|
|
15718
|
+
...current.data,
|
|
15719
|
+
revision: newRevision(),
|
|
15720
|
+
root: this.root,
|
|
15721
|
+
runtime: stampNew(current.data)
|
|
15722
|
+
} : {
|
|
15660
15723
|
schemaVersion: SCHEMA_VERSION,
|
|
15661
15724
|
id: newId(),
|
|
15662
15725
|
revision: newRevision(),
|
|
15663
15726
|
root: this.root,
|
|
15727
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15664
15728
|
writer: null
|
|
15665
15729
|
};
|
|
15666
15730
|
const timestamp = value.now ?? now();
|
|
@@ -15682,6 +15746,7 @@ class TaskStore {
|
|
|
15682
15746
|
status: "active",
|
|
15683
15747
|
closure: null,
|
|
15684
15748
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
15749
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15685
15750
|
assessments: [],
|
|
15686
15751
|
policy: null,
|
|
15687
15752
|
policyChanges: [],
|
|
@@ -15712,7 +15777,7 @@ class TaskStore {
|
|
|
15712
15777
|
if (!current.ok)
|
|
15713
15778
|
return current;
|
|
15714
15779
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
15715
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
15780
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15716
15781
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
15717
15782
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
15718
15783
|
});
|
|
@@ -15720,11 +15785,17 @@ class TaskStore {
|
|
|
15720
15785
|
if (current.data && !previousWorkspaceBytes)
|
|
15721
15786
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
15722
15787
|
const timestamp = input.now ?? now();
|
|
15723
|
-
const workspace = current.data ? {
|
|
15788
|
+
const workspace = current.data ? {
|
|
15789
|
+
...current.data,
|
|
15790
|
+
revision: newRevision(),
|
|
15791
|
+
root: this.root,
|
|
15792
|
+
runtime: stampNew(current.data)
|
|
15793
|
+
} : {
|
|
15724
15794
|
schemaVersion: SCHEMA_VERSION,
|
|
15725
15795
|
id: input.workspaceId ?? newId(),
|
|
15726
15796
|
revision: newRevision(),
|
|
15727
15797
|
root: this.root,
|
|
15798
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15728
15799
|
writer: null
|
|
15729
15800
|
};
|
|
15730
15801
|
const task = {
|
|
@@ -15732,7 +15803,11 @@ class TaskStore {
|
|
|
15732
15803
|
workspaceId: workspace.id,
|
|
15733
15804
|
revision: newRevision(),
|
|
15734
15805
|
createdAt: timestamp,
|
|
15735
|
-
updatedAt: timestamp
|
|
15806
|
+
updatedAt: timestamp,
|
|
15807
|
+
runtime: {
|
|
15808
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
15809
|
+
updatedWith: runtimeVersion()
|
|
15810
|
+
}
|
|
15736
15811
|
};
|
|
15737
15812
|
const validTask = taskRecordSchema.safeParse(task);
|
|
15738
15813
|
if (!validTask.success)
|
|
@@ -15781,7 +15856,8 @@ class TaskStore {
|
|
|
15781
15856
|
workspaceId: current.data.workspaceId,
|
|
15782
15857
|
createdAt: current.data.createdAt,
|
|
15783
15858
|
revision: context.revision,
|
|
15784
|
-
updatedAt: context.now
|
|
15859
|
+
updatedAt: context.now,
|
|
15860
|
+
runtime: stampNew(current.data)
|
|
15785
15861
|
};
|
|
15786
15862
|
const valid = taskRecordSchema.safeParse(record);
|
|
15787
15863
|
if (!valid.success)
|
|
@@ -15815,7 +15891,8 @@ class TaskStore {
|
|
|
15815
15891
|
...changed.data,
|
|
15816
15892
|
id: current.data.id,
|
|
15817
15893
|
root: this.root,
|
|
15818
|
-
revision: context.revision
|
|
15894
|
+
revision: context.revision,
|
|
15895
|
+
runtime: stampNew(current.data)
|
|
15819
15896
|
};
|
|
15820
15897
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
15821
15898
|
if (!valid.success)
|
|
@@ -15858,7 +15935,8 @@ class TaskStore {
|
|
|
15858
15935
|
...changedWorkspace.data,
|
|
15859
15936
|
id: workspace.data.id,
|
|
15860
15937
|
root: this.root,
|
|
15861
|
-
revision: workspaceContext.revision
|
|
15938
|
+
revision: workspaceContext.revision,
|
|
15939
|
+
runtime: stampNew(workspace.data)
|
|
15862
15940
|
});
|
|
15863
15941
|
if (!nextWorkspace.success)
|
|
15864
15942
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -15883,7 +15961,8 @@ class TaskStore {
|
|
|
15883
15961
|
workspaceId: task.data.workspaceId,
|
|
15884
15962
|
createdAt: task.data.createdAt,
|
|
15885
15963
|
revision: taskContext.revision,
|
|
15886
|
-
updatedAt: taskContext.now
|
|
15964
|
+
updatedAt: taskContext.now,
|
|
15965
|
+
runtime: stampNew(task.data)
|
|
15887
15966
|
});
|
|
15888
15967
|
if (!nextTask.success) {
|
|
15889
15968
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -15910,7 +15989,7 @@ class TaskStore {
|
|
|
15910
15989
|
if (match)
|
|
15911
15990
|
candidates.push({
|
|
15912
15991
|
target: match[1],
|
|
15913
|
-
path:
|
|
15992
|
+
path: path4.join(this.recoveryDir, name),
|
|
15914
15993
|
digest: match[3]
|
|
15915
15994
|
});
|
|
15916
15995
|
}
|
|
@@ -16103,7 +16182,8 @@ class TaskStore {
|
|
|
16103
16182
|
const value = {
|
|
16104
16183
|
...workspace,
|
|
16105
16184
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
16106
|
-
revision: newRevision()
|
|
16185
|
+
revision: newRevision(),
|
|
16186
|
+
runtime: stampNew(workspace)
|
|
16107
16187
|
};
|
|
16108
16188
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
16109
16189
|
}
|
|
@@ -16181,7 +16261,7 @@ class TaskStore {
|
|
|
16181
16261
|
}
|
|
16182
16262
|
fs4.renameSync(temporary, file);
|
|
16183
16263
|
temporary = undefined;
|
|
16184
|
-
this.fsyncDirectory(
|
|
16264
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
16185
16265
|
return success(null, null, value);
|
|
16186
16266
|
} catch (error) {
|
|
16187
16267
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -16195,9 +16275,9 @@ class TaskStore {
|
|
|
16195
16275
|
}
|
|
16196
16276
|
}
|
|
16197
16277
|
saveRecovery(file, bytes) {
|
|
16198
|
-
const target =
|
|
16199
|
-
const id = target === "task" ?
|
|
16200
|
-
const destination =
|
|
16278
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
16279
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
16280
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
16201
16281
|
if (fs4.existsSync(destination)) {
|
|
16202
16282
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
16203
16283
|
return;
|
|
@@ -16275,7 +16355,7 @@ class TaskStore {
|
|
|
16275
16355
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
16276
16356
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
16277
16357
|
return null;
|
|
16278
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
16358
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
16279
16359
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
16280
16360
|
return null;
|
|
16281
16361
|
ids.add(parsed.data.workspaceId);
|
|
@@ -16317,7 +16397,12 @@ class TaskStore {
|
|
|
16317
16397
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
16318
16398
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
16319
16399
|
const parsed = schema.safeParse(value);
|
|
16320
|
-
|
|
16400
|
+
if (parsed.success)
|
|
16401
|
+
return success(null, null, parsed.data);
|
|
16402
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
16403
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
16404
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
16405
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
16321
16406
|
}
|
|
16322
16407
|
processStart(pid) {
|
|
16323
16408
|
try {
|
|
@@ -16327,43 +16412,98 @@ class TaskStore {
|
|
|
16327
16412
|
}
|
|
16328
16413
|
}
|
|
16329
16414
|
conflict(expected, actual) {
|
|
16330
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
16415
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
16331
16416
|
expectedRevision: expected,
|
|
16332
16417
|
actualRevision: actual
|
|
16333
16418
|
});
|
|
16334
16419
|
}
|
|
16335
16420
|
taskPath(taskId) {
|
|
16336
|
-
return
|
|
16421
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
16337
16422
|
}
|
|
16338
16423
|
get workitDir() {
|
|
16339
|
-
return
|
|
16424
|
+
return path4.join(this.root, ".workit");
|
|
16340
16425
|
}
|
|
16341
16426
|
get tasksDir() {
|
|
16342
|
-
return
|
|
16427
|
+
return path4.join(this.workitDir, "tasks");
|
|
16343
16428
|
}
|
|
16344
16429
|
get recoveryDir() {
|
|
16345
|
-
return
|
|
16430
|
+
return path4.join(this.workitDir, "recovery");
|
|
16346
16431
|
}
|
|
16347
16432
|
get workspacePath() {
|
|
16348
|
-
return
|
|
16433
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
16349
16434
|
}
|
|
16350
16435
|
get lockPath() {
|
|
16351
|
-
return
|
|
16436
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
16352
16437
|
}
|
|
16353
16438
|
get gitignorePath() {
|
|
16354
|
-
return
|
|
16439
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
16355
16440
|
}
|
|
16356
16441
|
}
|
|
16442
|
+
// packages/workit-core/src/core/workers.ts
|
|
16443
|
+
var same = (left, right) => {
|
|
16444
|
+
try {
|
|
16445
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
16446
|
+
} catch {
|
|
16447
|
+
return false;
|
|
16448
|
+
}
|
|
16449
|
+
};
|
|
16450
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
16451
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
16452
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
16453
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
16454
|
+
const code = char.charCodeAt(0);
|
|
16455
|
+
return code < 32 || code === 127;
|
|
16456
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
16457
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
16458
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
16459
|
+
function assertProductWriteAllowed(input) {
|
|
16460
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
16461
|
+
return failure("invalid_input", "invalid product write path");
|
|
16462
|
+
if (!input.store)
|
|
16463
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
16464
|
+
const task = input.store.readTask(input.task.id);
|
|
16465
|
+
if (!task.ok)
|
|
16466
|
+
return task;
|
|
16467
|
+
const workspace = input.store.readWorkspace();
|
|
16468
|
+
if (!workspace.ok)
|
|
16469
|
+
return workspace;
|
|
16470
|
+
if (!workspace.data)
|
|
16471
|
+
return failure("not_found", "workspace not found");
|
|
16472
|
+
const workspaceRecord = workspace.data;
|
|
16473
|
+
if (task.data.status !== "active")
|
|
16474
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
16475
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
16476
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
16477
|
+
const writer = workspaceRecord.writer;
|
|
16478
|
+
if (!writer)
|
|
16479
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
16480
|
+
if (writer.state === "uncertain")
|
|
16481
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
16482
|
+
owner: writer.owner
|
|
16483
|
+
});
|
|
16484
|
+
const caller = callerValue(input.caller);
|
|
16485
|
+
const workerId = workerIdOf(input.caller);
|
|
16486
|
+
const owner = writer.owner;
|
|
16487
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
16488
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
16489
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
16490
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
16491
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
16492
|
+
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))))
|
|
16493
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
16494
|
+
return success(null, workspaceRecord.revision, owner);
|
|
16495
|
+
}
|
|
16496
|
+
|
|
16357
16497
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16358
16498
|
import { createHash as createHash4 } from "node:crypto";
|
|
16359
16499
|
import * as fs6 from "node:fs";
|
|
16360
16500
|
import { spawnSync } from "node:child_process";
|
|
16361
|
-
import
|
|
16501
|
+
import path6 from "node:path";
|
|
16362
16502
|
|
|
16363
16503
|
// packages/workit-core/src/core/authority.ts
|
|
16364
16504
|
import { createHash as createHash3 } from "node:crypto";
|
|
16365
16505
|
import * as fs5 from "node:fs";
|
|
16366
|
-
import
|
|
16506
|
+
import path5 from "node:path";
|
|
16367
16507
|
var verifiedAuthorities = new WeakMap;
|
|
16368
16508
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
16369
16509
|
const token = {};
|
|
@@ -16490,20 +16630,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
16490
16630
|
continue;
|
|
16491
16631
|
if (!reference.digest)
|
|
16492
16632
|
return failure("invalid_input", "document references require a byte digest");
|
|
16493
|
-
const target =
|
|
16494
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
16495
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
16633
|
+
const target = path5.resolve(root, reference.path);
|
|
16634
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
16635
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
16636
|
+
fields: [
|
|
16637
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
16638
|
+
]
|
|
16639
|
+
});
|
|
16496
16640
|
try {
|
|
16497
|
-
const relative =
|
|
16641
|
+
const relative = path5.relative(root, target);
|
|
16498
16642
|
let current = root;
|
|
16499
|
-
for (const segment of relative.split(
|
|
16643
|
+
for (const segment of relative.split(path5.sep)) {
|
|
16500
16644
|
if (!segment)
|
|
16501
16645
|
continue;
|
|
16502
|
-
current =
|
|
16646
|
+
current = path5.join(current, segment);
|
|
16503
16647
|
const stat = fs5.lstatSync(current);
|
|
16504
16648
|
const real = fs5.realpathSync(current);
|
|
16505
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
16506
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
16649
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
16650
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
16651
|
+
fields: [
|
|
16652
|
+
{
|
|
16653
|
+
path: "contentRefs.path",
|
|
16654
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
16655
|
+
}
|
|
16656
|
+
]
|
|
16657
|
+
});
|
|
16507
16658
|
if (stat.isSymbolicLink())
|
|
16508
16659
|
return failure("invalid_input", "document reference uses a symlink");
|
|
16509
16660
|
if (current === target && !stat.isFile())
|
|
@@ -16850,14 +17001,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
16850
17001
|
|
|
16851
17002
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16852
17003
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
16853
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
16854
|
-
var relative = (root, candidate) =>
|
|
17004
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
17005
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
16855
17006
|
var canonicalPath = (value) => {
|
|
16856
17007
|
if (typeof value !== "string" || !value)
|
|
16857
17008
|
return null;
|
|
16858
17009
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
16859
17010
|
return null;
|
|
16860
|
-
const normalized =
|
|
17011
|
+
const normalized = path6.posix.normalize(value);
|
|
16861
17012
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
16862
17013
|
return null;
|
|
16863
17014
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -16905,14 +17056,14 @@ var gitPaths = (root) => {
|
|
|
16905
17056
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
16906
17057
|
};
|
|
16907
17058
|
}
|
|
16908
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
17059
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
16909
17060
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
16910
17061
|
if (stagedDeleted.status !== 0) {
|
|
16911
17062
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
16912
17063
|
if (!/not a git repository/i.test(stderr))
|
|
16913
17064
|
return { paths, uncertain: true, git: true };
|
|
16914
17065
|
} else if (stagedDeleted.stdout) {
|
|
16915
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
17066
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
16916
17067
|
}
|
|
16917
17068
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
16918
17069
|
};
|
|
@@ -16927,7 +17078,7 @@ var walk = (root, directory, output) => {
|
|
|
16927
17078
|
for (const name of names) {
|
|
16928
17079
|
if (name === ".git" || name === ".workit")
|
|
16929
17080
|
continue;
|
|
16930
|
-
const target =
|
|
17081
|
+
const target = path6.join(directory, name);
|
|
16931
17082
|
let stat;
|
|
16932
17083
|
try {
|
|
16933
17084
|
stat = fs6.lstatSync(target);
|
|
@@ -16945,17 +17096,17 @@ var walk = (root, directory, output) => {
|
|
|
16945
17096
|
var scopeRoots = (root, scope) => {
|
|
16946
17097
|
const roots = [];
|
|
16947
17098
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
16948
|
-
const target =
|
|
17099
|
+
const target = path6.resolve(root, item);
|
|
16949
17100
|
if (!inside(root, target))
|
|
16950
|
-
return failure("invalid_input",
|
|
17101
|
+
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` }] });
|
|
16951
17102
|
let ancestor = target;
|
|
16952
17103
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
16953
|
-
ancestor =
|
|
17104
|
+
ancestor = path6.dirname(ancestor);
|
|
16954
17105
|
try {
|
|
16955
17106
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
16956
|
-
return failure("invalid_input",
|
|
17107
|
+
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` }] });
|
|
16957
17108
|
} catch {
|
|
16958
|
-
return failure("invalid_input",
|
|
17109
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
16959
17110
|
}
|
|
16960
17111
|
roots.push(target);
|
|
16961
17112
|
}
|
|
@@ -16970,7 +17121,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
16970
17121
|
}
|
|
16971
17122
|
const normalizedScope = canonicalScope(scope);
|
|
16972
17123
|
if (!normalizedScope)
|
|
16973
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
17124
|
+
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" }] });
|
|
16974
17125
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
16975
17126
|
if (!roots.ok)
|
|
16976
17127
|
return roots;
|
|
@@ -17002,7 +17153,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
17002
17153
|
for (const item of [...names].sort()) {
|
|
17003
17154
|
if (!scopeMatches(item, normalizedScope))
|
|
17004
17155
|
continue;
|
|
17005
|
-
const target =
|
|
17156
|
+
const target = path6.join(checkout, item);
|
|
17006
17157
|
try {
|
|
17007
17158
|
const stat = fs6.lstatSync(target);
|
|
17008
17159
|
if (stat.isSymbolicLink()) {
|
|
@@ -17159,7 +17310,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
17159
17310
|
return true;
|
|
17160
17311
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
17161
17312
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
17162
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
17313
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
17163
17314
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
17164
17315
|
});
|
|
17165
17316
|
if (passed.length) {
|
|
@@ -17255,7 +17406,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
17255
17406
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
17256
17407
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
17257
17408
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
17258
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
17409
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
17410
|
+
requirements: blocking.map((item) => {
|
|
17411
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
17412
|
+
return {
|
|
17413
|
+
requirementId: item.requirementId,
|
|
17414
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
17415
|
+
reason: item.reason,
|
|
17416
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
17417
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
17418
|
+
};
|
|
17419
|
+
})
|
|
17259
17420
|
});
|
|
17260
17421
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
17261
17422
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -17291,7 +17452,7 @@ function reconcileResume(view, observations = []) {
|
|
|
17291
17452
|
return candidate;
|
|
17292
17453
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
17293
17454
|
const blockers = [...view.task.progress.blockers];
|
|
17294
|
-
if (view.task.workers.some((entry) =>
|
|
17455
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
17295
17456
|
blockers.push({
|
|
17296
17457
|
reason: "worker state requires reconciliation",
|
|
17297
17458
|
dependentAction: "resume",
|
|
@@ -17324,7 +17485,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
17324
17485
|
}).strict(),
|
|
17325
17486
|
object({
|
|
17326
17487
|
operation: literal("git.commit"),
|
|
17327
|
-
payload: object({
|
|
17488
|
+
payload: object({
|
|
17489
|
+
message: string2().min(1).optional(),
|
|
17490
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
17491
|
+
plan_branch: string2().min(1).optional()
|
|
17492
|
+
}).strict()
|
|
17328
17493
|
}).strict(),
|
|
17329
17494
|
object({
|
|
17330
17495
|
operation: literal("git.push"),
|
|
@@ -17559,7 +17724,7 @@ function resolveRequirements(input) {
|
|
|
17559
17724
|
].join(", ")}`,
|
|
17560
17725
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
17561
17726
|
before: "dependent_action",
|
|
17562
|
-
dependentAction:
|
|
17727
|
+
dependentAction: null,
|
|
17563
17728
|
acceptanceAllowed: false
|
|
17564
17729
|
}));
|
|
17565
17730
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -17570,7 +17735,7 @@ function resolveRequirements(input) {
|
|
|
17570
17735
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
17571
17736
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
17572
17737
|
before: "dependent_action",
|
|
17573
|
-
dependentAction:
|
|
17738
|
+
dependentAction: null,
|
|
17574
17739
|
acceptanceAllowed: false
|
|
17575
17740
|
}));
|
|
17576
17741
|
if (signals.behaviorChange.value === true)
|
|
@@ -17728,59 +17893,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
17728
17893
|
return parsed.data;
|
|
17729
17894
|
}
|
|
17730
17895
|
|
|
17731
|
-
// packages/workit-core/src/core/workers.ts
|
|
17732
|
-
var same = (left, right) => {
|
|
17733
|
-
try {
|
|
17734
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
17735
|
-
} catch {
|
|
17736
|
-
return false;
|
|
17737
|
-
}
|
|
17738
|
-
};
|
|
17739
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
17740
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
17741
|
-
const code = char.charCodeAt(0);
|
|
17742
|
-
return code < 32 || code === 127;
|
|
17743
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
17744
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
17745
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
17746
|
-
function assertProductWriteAllowed(input) {
|
|
17747
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
17748
|
-
return failure("invalid_input", "invalid product write path");
|
|
17749
|
-
if (!input.store)
|
|
17750
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
17751
|
-
const task = input.store.readTask(input.task.id);
|
|
17752
|
-
if (!task.ok)
|
|
17753
|
-
return task;
|
|
17754
|
-
const workspace = input.store.readWorkspace();
|
|
17755
|
-
if (!workspace.ok)
|
|
17756
|
-
return workspace;
|
|
17757
|
-
if (!workspace.data)
|
|
17758
|
-
return failure("not_found", "workspace not found");
|
|
17759
|
-
const workspaceRecord = workspace.data;
|
|
17760
|
-
if (task.data.status !== "active")
|
|
17761
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
17762
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
17763
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
17764
|
-
const writer = workspaceRecord.writer;
|
|
17765
|
-
if (!writer)
|
|
17766
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
17767
|
-
if (writer.state === "uncertain")
|
|
17768
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
17769
|
-
owner: writer.owner
|
|
17770
|
-
});
|
|
17771
|
-
const caller = callerValue(input.caller);
|
|
17772
|
-
const workerId = workerIdOf(input.caller);
|
|
17773
|
-
const owner = writer.owner;
|
|
17774
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
17775
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
17776
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
17777
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
17778
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
17779
|
-
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))))
|
|
17780
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
17781
|
-
return success(null, workspaceRecord.revision, owner);
|
|
17782
|
-
}
|
|
17783
|
-
|
|
17784
17896
|
// packages/workit-core/src/core/task-engine.ts
|
|
17785
17897
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
17786
17898
|
kind,
|
|
@@ -17911,15 +18023,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
17911
18023
|
return failure("invalid_transition", "worker has not started");
|
|
17912
18024
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
17913
18025
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
18026
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
17914
18027
|
const nextEntry = {
|
|
17915
18028
|
...entry,
|
|
17916
18029
|
recordedAt: input.now ?? entry.recordedAt,
|
|
17917
18030
|
provenance: authority.provenance,
|
|
17918
|
-
data: {
|
|
18031
|
+
data: {
|
|
18032
|
+
...entry.data,
|
|
18033
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
18034
|
+
session: input.session
|
|
18035
|
+
}
|
|
17919
18036
|
};
|
|
17920
18037
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
17921
18038
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
17922
|
-
if (entry.data.state ===
|
|
18039
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
17923
18040
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
17924
18041
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
17925
18042
|
taskId: task.data.id,
|
|
@@ -18289,7 +18406,7 @@ class WorkitCore {
|
|
|
18289
18406
|
activeWorkerBlocker(task, workspace) {
|
|
18290
18407
|
if (workspace.writer)
|
|
18291
18408
|
return failure("recovery_required", "writer ownership must be released first");
|
|
18292
|
-
if (task.workers.some((entry) =>
|
|
18409
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
18293
18410
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
18294
18411
|
return null;
|
|
18295
18412
|
}
|
|
@@ -18526,11 +18643,6 @@ class WorkitCore {
|
|
|
18526
18643
|
if (helper && !helper.ok)
|
|
18527
18644
|
return helper;
|
|
18528
18645
|
const evidence = input.evidence;
|
|
18529
|
-
if (helper?.ok) {
|
|
18530
|
-
const assignment = helper.data.data.assignment;
|
|
18531
|
-
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))
|
|
18532
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
18533
|
-
}
|
|
18534
18646
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
18535
18647
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
18536
18648
|
if (evidence.kind === "review") {
|
|
@@ -18547,6 +18659,11 @@ class WorkitCore {
|
|
|
18547
18659
|
evidence.beforeCandidateId ??= null;
|
|
18548
18660
|
evidence.candidateId ??= null;
|
|
18549
18661
|
}
|
|
18662
|
+
if (helper?.ok) {
|
|
18663
|
+
const assignment = helper.data.data.assignment;
|
|
18664
|
+
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))
|
|
18665
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
18666
|
+
}
|
|
18550
18667
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
18551
18668
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
18552
18669
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -19442,6 +19559,9 @@ class WorkitCore {
|
|
|
19442
19559
|
id: task.id,
|
|
19443
19560
|
revision: task.revision,
|
|
19444
19561
|
workspaceRevision: workspace.data.revision,
|
|
19562
|
+
createdAt: task.createdAt,
|
|
19563
|
+
updatedAt: task.updatedAt,
|
|
19564
|
+
runtime: task.runtime ?? null,
|
|
19445
19565
|
objective: task.intent.data.objective,
|
|
19446
19566
|
status: task.status,
|
|
19447
19567
|
closure: task.closure,
|
|
@@ -19527,7 +19647,7 @@ class WorkitCore {
|
|
|
19527
19647
|
const base = reconcileResume(effectiveView);
|
|
19528
19648
|
if (!base.ok)
|
|
19529
19649
|
return base;
|
|
19530
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
19650
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
19531
19651
|
const blockers = [...base.data.blockers];
|
|
19532
19652
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
19533
19653
|
blockers.push({
|
|
@@ -19594,7 +19714,8 @@ class WorkitCore {
|
|
|
19594
19714
|
...current,
|
|
19595
19715
|
status,
|
|
19596
19716
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
19597
|
-
progress:
|
|
19717
|
+
progress: current.progress,
|
|
19718
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
19598
19719
|
})
|
|
19599
19720
|
});
|
|
19600
19721
|
if (!changed.ok)
|
|
@@ -19630,7 +19751,7 @@ class WorkitCore {
|
|
|
19630
19751
|
return workspace;
|
|
19631
19752
|
if (!workspace.data)
|
|
19632
19753
|
return failure("not_found", "workspace not found");
|
|
19633
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
19754
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
19634
19755
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
19635
19756
|
this.fillRevisions(input, task.data, workspace.data);
|
|
19636
19757
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -19741,15 +19862,15 @@ function resolveGitRevision(root, value) {
|
|
|
19741
19862
|
}
|
|
19742
19863
|
function resolveInside(root, candidate) {
|
|
19743
19864
|
const base = realpathSync5(root);
|
|
19744
|
-
const target =
|
|
19745
|
-
if (target !== base && !target.startsWith(base +
|
|
19865
|
+
const target = path7.resolve(base, candidate);
|
|
19866
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
19746
19867
|
throw new Error("path must stay inside repository root");
|
|
19747
19868
|
}
|
|
19748
19869
|
let ancestor = target;
|
|
19749
|
-
while (!
|
|
19750
|
-
ancestor =
|
|
19870
|
+
while (!existsSync4(ancestor))
|
|
19871
|
+
ancestor = path7.dirname(ancestor);
|
|
19751
19872
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
19752
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
19873
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
19753
19874
|
throw new Error("path must stay inside repository root");
|
|
19754
19875
|
}
|
|
19755
19876
|
return target;
|
|
@@ -25796,14 +25917,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25796
25917
|
import {
|
|
25797
25918
|
copyFileSync,
|
|
25798
25919
|
cpSync,
|
|
25799
|
-
existsSync as
|
|
25920
|
+
existsSync as existsSync5,
|
|
25800
25921
|
mkdirSync as mkdirSync2,
|
|
25801
25922
|
readFileSync as readFileSync4,
|
|
25802
25923
|
readdirSync as readdirSync3,
|
|
25803
25924
|
writeFileSync as writeFileSync2
|
|
25804
25925
|
} from "node:fs";
|
|
25805
25926
|
import os2 from "node:os";
|
|
25806
|
-
import
|
|
25927
|
+
import path8 from "node:path";
|
|
25807
25928
|
var diagnosticLogger;
|
|
25808
25929
|
var PRESETS = {
|
|
25809
25930
|
gitflow: {
|
|
@@ -25824,7 +25945,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25824
25945
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25825
25946
|
};
|
|
25826
25947
|
};
|
|
25827
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
25948
|
+
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");
|
|
25828
25949
|
var migratedDir = null;
|
|
25829
25950
|
var migrationFailed = false;
|
|
25830
25951
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25834,12 +25955,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25834
25955
|
migratedDir = dir;
|
|
25835
25956
|
return dir;
|
|
25836
25957
|
}
|
|
25837
|
-
const legacy =
|
|
25838
|
-
if (!
|
|
25958
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25959
|
+
if (!existsSync5(legacy)) {
|
|
25839
25960
|
migratedDir = dir;
|
|
25840
25961
|
return dir;
|
|
25841
25962
|
}
|
|
25842
|
-
if (!migrationFailed &&
|
|
25963
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25843
25964
|
migratedDir = dir;
|
|
25844
25965
|
return dir;
|
|
25845
25966
|
}
|
|
@@ -25847,9 +25968,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25847
25968
|
mkdirSync2(dir, { recursive: true });
|
|
25848
25969
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25849
25970
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25850
|
-
const src =
|
|
25851
|
-
const dest =
|
|
25852
|
-
if (
|
|
25971
|
+
const src = path8.join(legacy, entry.name);
|
|
25972
|
+
const dest = path8.join(dir, entry.name);
|
|
25973
|
+
if (existsSync5(dest))
|
|
25853
25974
|
continue;
|
|
25854
25975
|
try {
|
|
25855
25976
|
if (entry.isDirectory())
|
|
@@ -25922,7 +26043,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25922
26043
|
};
|
|
25923
26044
|
};
|
|
25924
26045
|
var readConfigTyped = (dir) => {
|
|
25925
|
-
const file =
|
|
26046
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25926
26047
|
return parseConfigResult(readSafe(file), file);
|
|
25927
26048
|
};
|
|
25928
26049
|
var readConfig = () => {
|
|
@@ -25958,9 +26079,9 @@ var COMMIT_PRESETS = [
|
|
|
25958
26079
|
|
|
25959
26080
|
// packages/workit-core/src/core/workspaces.ts
|
|
25960
26081
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25961
|
-
import
|
|
26082
|
+
import path9 from "node:path";
|
|
25962
26083
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25963
|
-
const file =
|
|
26084
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25964
26085
|
let raw;
|
|
25965
26086
|
try {
|
|
25966
26087
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26058,10 +26179,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26058
26179
|
|
|
26059
26180
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26060
26181
|
import fs7 from "node:fs";
|
|
26061
|
-
import
|
|
26182
|
+
import path10 from "node:path";
|
|
26062
26183
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26063
26184
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26064
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26185
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26065
26186
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26066
26187
|
var remoteProvider = (cwd) => {
|
|
26067
26188
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26144,8 +26265,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26144
26265
|
}
|
|
26145
26266
|
const prov = cfg[provider] ?? {};
|
|
26146
26267
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26147
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26148
|
-
const tokenPath =
|
|
26268
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26269
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26149
26270
|
let tokenOk = false;
|
|
26150
26271
|
if (fs7.existsSync(tokenPath)) {
|
|
26151
26272
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26154,7 +26275,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26154
26275
|
}
|
|
26155
26276
|
const out = {
|
|
26156
26277
|
ok: true,
|
|
26157
|
-
configPath:
|
|
26278
|
+
configPath: path10.resolve(cfgPath),
|
|
26158
26279
|
provider,
|
|
26159
26280
|
defaultTargetBranch: defaultTarget,
|
|
26160
26281
|
pr: cfg.pr ?? {},
|
|
@@ -26247,18 +26368,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26247
26368
|
|
|
26248
26369
|
// packages/workit-core/src/core/pr-create.ts
|
|
26249
26370
|
import fs8 from "node:fs";
|
|
26250
|
-
import
|
|
26371
|
+
import path11 from "node:path";
|
|
26251
26372
|
function parseGhIssue(value) {
|
|
26252
26373
|
const m = /issues\/(\d+)/.exec(value);
|
|
26253
26374
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26254
26375
|
}
|
|
26255
26376
|
function whichOnPath(tool) {
|
|
26256
26377
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26257
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26378
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26258
26379
|
if (!dir)
|
|
26259
26380
|
continue;
|
|
26260
26381
|
for (const name of names) {
|
|
26261
|
-
const candidate =
|
|
26382
|
+
const candidate = path11.join(dir, name);
|
|
26262
26383
|
try {
|
|
26263
26384
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26264
26385
|
return candidate;
|
|
@@ -26271,8 +26392,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26271
26392
|
|
|
26272
26393
|
// packages/workit-core/src/core/repo-context.ts
|
|
26273
26394
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26274
|
-
import { existsSync as
|
|
26275
|
-
import
|
|
26395
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26396
|
+
import path12 from "node:path";
|
|
26276
26397
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26277
26398
|
try {
|
|
26278
26399
|
gitRevisionParts(value);
|
|
@@ -26406,7 +26527,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26406
26527
|
]) {
|
|
26407
26528
|
const parts = rel.split("/");
|
|
26408
26529
|
const base = parts[parts.length - 1];
|
|
26409
|
-
const dir = parts.length > 1 ?
|
|
26530
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26410
26531
|
const actual = probeCaseInsensitive(dir, base);
|
|
26411
26532
|
if (actual)
|
|
26412
26533
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26437,8 +26558,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26437
26558
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26438
26559
|
};
|
|
26439
26560
|
var packageScripts = (cwd, keys) => {
|
|
26440
|
-
const pkgPath =
|
|
26441
|
-
if (!
|
|
26561
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26562
|
+
if (!existsSync6(pkgPath))
|
|
26442
26563
|
return [];
|
|
26443
26564
|
try {
|
|
26444
26565
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26463,9 +26584,9 @@ var documentationFiles = (cwd) => {
|
|
|
26463
26584
|
if (entry.isDirectory()) {
|
|
26464
26585
|
if (excluded.has(entry.name))
|
|
26465
26586
|
continue;
|
|
26466
|
-
walk(
|
|
26587
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26467
26588
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26468
|
-
matches.push("./" +
|
|
26589
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26469
26590
|
}
|
|
26470
26591
|
}
|
|
26471
26592
|
};
|
|
@@ -26473,7 +26594,7 @@ var documentationFiles = (cwd) => {
|
|
|
26473
26594
|
return matches.sort().slice(0, 200);
|
|
26474
26595
|
};
|
|
26475
26596
|
var readTrimmed = (file, maxLines) => {
|
|
26476
|
-
if (!
|
|
26597
|
+
if (!existsSync6(file))
|
|
26477
26598
|
return "";
|
|
26478
26599
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26479
26600
|
`);
|
|
@@ -26481,7 +26602,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26481
26602
|
`);
|
|
26482
26603
|
};
|
|
26483
26604
|
function prReadyContext(root, range) {
|
|
26484
|
-
const cwd =
|
|
26605
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26485
26606
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26486
26607
|
return invalidRange(cwd);
|
|
26487
26608
|
let stdout = "";
|
|
@@ -26539,7 +26660,7 @@ ${status.stderr}` : "");
|
|
|
26539
26660
|
stdout += `template_path: ${template}
|
|
26540
26661
|
|
|
26541
26662
|
`;
|
|
26542
|
-
stdout += readTrimmed(
|
|
26663
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26543
26664
|
} else {
|
|
26544
26665
|
stdout += `template_path: none
|
|
26545
26666
|
|
|
@@ -26549,7 +26670,7 @@ ${status.stderr}` : "");
|
|
|
26549
26670
|
stdout += `
|
|
26550
26671
|
`;
|
|
26551
26672
|
stdout += printSection("Recent Validation Signals");
|
|
26552
|
-
if (
|
|
26673
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26553
26674
|
stdout += `package.json detected. Common scripts:
|
|
26554
26675
|
`;
|
|
26555
26676
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26558,7 +26679,7 @@ ${status.stderr}` : "");
|
|
|
26558
26679
|
`) + `
|
|
26559
26680
|
`;
|
|
26560
26681
|
}
|
|
26561
|
-
if (
|
|
26682
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26562
26683
|
stdout += `Rust project detected.
|
|
26563
26684
|
`;
|
|
26564
26685
|
}
|
|
@@ -26591,7 +26712,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26591
26712
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26592
26713
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26593
26714
|
function changelogContext(root, range) {
|
|
26594
|
-
const cwd =
|
|
26715
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26595
26716
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26596
26717
|
return invalidRange(cwd);
|
|
26597
26718
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26609,8 +26730,8 @@ function changelogContext(root, range) {
|
|
|
26609
26730
|
stdout += CHANGELOG_RULES + `
|
|
26610
26731
|
`;
|
|
26611
26732
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26612
|
-
const changelogPath =
|
|
26613
|
-
if (
|
|
26733
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26734
|
+
if (existsSync6(changelogPath)) {
|
|
26614
26735
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26615
26736
|
`;
|
|
26616
26737
|
} else {
|
|
@@ -26629,7 +26750,7 @@ function changelogContext(root, range) {
|
|
|
26629
26750
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26630
26751
|
}
|
|
26631
26752
|
function docsRefreshContext(root, range) {
|
|
26632
|
-
const cwd =
|
|
26753
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26633
26754
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26634
26755
|
return invalidRange(cwd);
|
|
26635
26756
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26651,8 +26772,8 @@ function docsRefreshContext(root, range) {
|
|
|
26651
26772
|
`) + `
|
|
26652
26773
|
`;
|
|
26653
26774
|
stdout += printSection("README Preview");
|
|
26654
|
-
const readme =
|
|
26655
|
-
if (
|
|
26775
|
+
const readme = path12.join(cwd, "README.md");
|
|
26776
|
+
if (existsSync6(readme)) {
|
|
26656
26777
|
stdout += readTrimmed(readme, 220) + `
|
|
26657
26778
|
`;
|
|
26658
26779
|
} else {
|
|
@@ -26660,8 +26781,8 @@ function docsRefreshContext(root, range) {
|
|
|
26660
26781
|
`;
|
|
26661
26782
|
}
|
|
26662
26783
|
stdout += printSection("Package Scripts");
|
|
26663
|
-
const pkgPath =
|
|
26664
|
-
if (
|
|
26784
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26785
|
+
if (existsSync6(pkgPath)) {
|
|
26665
26786
|
try {
|
|
26666
26787
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26667
26788
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26674,7 +26795,7 @@ function docsRefreshContext(root, range) {
|
|
|
26674
26795
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26675
26796
|
}
|
|
26676
26797
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26677
|
-
const cwd =
|
|
26798
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26678
26799
|
if (!rangeOrTag) {
|
|
26679
26800
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26680
26801
|
`, exitCode: 1, cwd };
|
|
@@ -26712,7 +26833,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26712
26833
|
`;
|
|
26713
26834
|
stdout += printSection("Existing Release Files");
|
|
26714
26835
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26715
|
-
if (
|
|
26836
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26716
26837
|
stdout += rel + `
|
|
26717
26838
|
`;
|
|
26718
26839
|
}
|
|
@@ -26903,25 +27024,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26903
27024
|
import fs10 from "node:fs";
|
|
26904
27025
|
import path13 from "node:path";
|
|
26905
27026
|
|
|
26906
|
-
// packages/workit-core/src/core/package-root.ts
|
|
26907
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
26908
|
-
import path12 from "node:path";
|
|
26909
|
-
import { fileURLToPath } from "node:url";
|
|
26910
|
-
var packageRoot = () => {
|
|
26911
|
-
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
26912
|
-
while (true) {
|
|
26913
|
-
const parent = path12.dirname(dir);
|
|
26914
|
-
if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
|
|
26915
|
-
return dir;
|
|
26916
|
-
dir = parent;
|
|
26917
|
-
}
|
|
26918
|
-
};
|
|
26919
|
-
var assetRoot = () => {
|
|
26920
|
-
const root = packageRoot();
|
|
26921
|
-
const assets = path12.join(root, "assets");
|
|
26922
|
-
return existsSync6(assets) ? assets : root;
|
|
26923
|
-
};
|
|
26924
|
-
|
|
26925
27027
|
// packages/workit-core/src/core/templates.ts
|
|
26926
27028
|
var repoRoot2 = assetRoot();
|
|
26927
27029
|
|
|
@@ -27425,6 +27527,32 @@ var toolInputSchema = (family) => {
|
|
|
27425
27527
|
const schema = boundedOperationJsonSchema(family);
|
|
27426
27528
|
return { type: "object", ...schema };
|
|
27427
27529
|
};
|
|
27530
|
+
var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
|
|
27531
|
+
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
27532
|
+
const action = branch.properties?.action?.const;
|
|
27533
|
+
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
27534
|
+
});
|
|
27535
|
+
var advertisedToolSchemas = (attested) => {
|
|
27536
|
+
if (attested)
|
|
27537
|
+
return OPERATION_FAMILIES.map((family) => ({
|
|
27538
|
+
name: `workit_${family}`,
|
|
27539
|
+
description: operationDescription(family),
|
|
27540
|
+
inputSchema: toolInputSchema(family)
|
|
27541
|
+
}));
|
|
27542
|
+
return OPERATION_FAMILIES.flatMap((family) => {
|
|
27543
|
+
const branches = readOnlyBranches(family);
|
|
27544
|
+
if (!branches.length)
|
|
27545
|
+
return [];
|
|
27546
|
+
const schema = boundedOperationJsonSchema(family);
|
|
27547
|
+
return [
|
|
27548
|
+
{
|
|
27549
|
+
name: `workit_${family}`,
|
|
27550
|
+
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27551
|
+
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
27552
|
+
}
|
|
27553
|
+
];
|
|
27554
|
+
});
|
|
27555
|
+
};
|
|
27428
27556
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27429
27557
|
if (result.ok)
|
|
27430
27558
|
return result;
|
|
@@ -27479,13 +27607,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27479
27607
|
].filter((file) => existsSync7(file)));
|
|
27480
27608
|
let staleWarned = false;
|
|
27481
27609
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27482
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
27487
|
-
}
|
|
27488
|
-
|
|
27610
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27611
|
+
let attested = false;
|
|
27612
|
+
try {
|
|
27613
|
+
const context = await contextProvider.current();
|
|
27614
|
+
attested = context.callerAttested === true;
|
|
27615
|
+
} catch {
|
|
27616
|
+
attested = false;
|
|
27617
|
+
}
|
|
27618
|
+
return { tools: advertisedToolSchemas(attested) };
|
|
27619
|
+
});
|
|
27489
27620
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27490
27621
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27491
27622
|
uri: `workit://context/${kind}`,
|
|
@@ -27600,7 +27731,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27600
27731
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27601
27732
|
if (!parsed.ok)
|
|
27602
27733
|
return resultForClient(parsed, workspaceRoot);
|
|
27603
|
-
if (context.callerAttested
|
|
27734
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27604
27735
|
return resultForClient({
|
|
27605
27736
|
ok: false,
|
|
27606
27737
|
schemaVersion: 1,
|
|
@@ -27660,6 +27791,13 @@ var codexCapabilities = (host, availability = {}) => {
|
|
|
27660
27791
|
reason: "file writes are host-policy; PreToolUse allows write tools",
|
|
27661
27792
|
refs: [ref(host, "PreToolUse")]
|
|
27662
27793
|
},
|
|
27794
|
+
{
|
|
27795
|
+
name: "fresh-context-review",
|
|
27796
|
+
surface: "SubagentStart",
|
|
27797
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
27798
|
+
reason: has("subagentStart") ? "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted" : "Codex subagent lifecycle hooks are unavailable for independent review",
|
|
27799
|
+
refs: [ref(host, "SubagentStart")]
|
|
27800
|
+
},
|
|
27663
27801
|
{
|
|
27664
27802
|
name: "native_subagents",
|
|
27665
27803
|
surface: "SubagentStart/SubagentStop",
|