@brainervirus/workit-codex 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +364 -221
- package/dist/workit-hook.js +433 -194
- package/package.json +3 -3
- package/skills/workit-babysit/SKILL.md +3 -1
- 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,
|
|
@@ -14457,7 +14458,7 @@ var workerReportSchema = object({
|
|
|
14457
14458
|
}).strict();
|
|
14458
14459
|
var workerSchema = object({
|
|
14459
14460
|
assignment: assignmentSchema,
|
|
14460
|
-
state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
14461
|
+
state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
14461
14462
|
session: refSchema.nullable(),
|
|
14462
14463
|
report: workerReportSchema.nullable()
|
|
14463
14464
|
}).strict();
|
|
@@ -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");
|
|
16440
|
+
}
|
|
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;
|
|
16355
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);
|
|
16356
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,
|
|
@@ -17899,10 +18011,10 @@ var applyWorkerLifecycle = (input) => {
|
|
|
17899
18011
|
const notStarted = input.dispatch === true;
|
|
17900
18012
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
17901
18013
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
17902
|
-
if (notStarted &&
|
|
17903
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
17904
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
18014
|
+
if (notStarted && entry.data.session !== null)
|
|
17905
18015
|
return failure("invalid_transition", "worker already has an observed session");
|
|
18016
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
18017
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
17906
18018
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
17907
18019
|
return failure("invalid_input", "running worker observations require a session");
|
|
17908
18020
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -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))
|
|
@@ -19053,7 +19170,12 @@ class WorkitCore {
|
|
|
19053
19170
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
19054
19171
|
return success(mutation.revision, null, {
|
|
19055
19172
|
...current,
|
|
19056
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
19173
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
19174
|
+
...item,
|
|
19175
|
+
recordedAt: mutation.now,
|
|
19176
|
+
provenance: attested.data,
|
|
19177
|
+
data: { ...item.data, state: "dispatching" }
|
|
19178
|
+
} : item)
|
|
19057
19179
|
});
|
|
19058
19180
|
}
|
|
19059
19181
|
});
|
|
@@ -19437,6 +19559,9 @@ class WorkitCore {
|
|
|
19437
19559
|
id: task.id,
|
|
19438
19560
|
revision: task.revision,
|
|
19439
19561
|
workspaceRevision: workspace.data.revision,
|
|
19562
|
+
createdAt: task.createdAt,
|
|
19563
|
+
updatedAt: task.updatedAt,
|
|
19564
|
+
runtime: task.runtime ?? null,
|
|
19440
19565
|
objective: task.intent.data.objective,
|
|
19441
19566
|
status: task.status,
|
|
19442
19567
|
closure: task.closure,
|
|
@@ -19522,7 +19647,7 @@ class WorkitCore {
|
|
|
19522
19647
|
const base = reconcileResume(effectiveView);
|
|
19523
19648
|
if (!base.ok)
|
|
19524
19649
|
return base;
|
|
19525
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
19650
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
19526
19651
|
const blockers = [...base.data.blockers];
|
|
19527
19652
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
19528
19653
|
blockers.push({
|
|
@@ -19589,7 +19714,8 @@ class WorkitCore {
|
|
|
19589
19714
|
...current,
|
|
19590
19715
|
status,
|
|
19591
19716
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
19592
|
-
progress:
|
|
19717
|
+
progress: current.progress,
|
|
19718
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
19593
19719
|
})
|
|
19594
19720
|
});
|
|
19595
19721
|
if (!changed.ok)
|
|
@@ -19625,7 +19751,7 @@ class WorkitCore {
|
|
|
19625
19751
|
return workspace;
|
|
19626
19752
|
if (!workspace.data)
|
|
19627
19753
|
return failure("not_found", "workspace not found");
|
|
19628
|
-
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)))
|
|
19629
19755
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
19630
19756
|
this.fillRevisions(input, task.data, workspace.data);
|
|
19631
19757
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -19736,15 +19862,15 @@ function resolveGitRevision(root, value) {
|
|
|
19736
19862
|
}
|
|
19737
19863
|
function resolveInside(root, candidate) {
|
|
19738
19864
|
const base = realpathSync5(root);
|
|
19739
|
-
const target =
|
|
19740
|
-
if (target !== base && !target.startsWith(base +
|
|
19865
|
+
const target = path7.resolve(base, candidate);
|
|
19866
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
19741
19867
|
throw new Error("path must stay inside repository root");
|
|
19742
19868
|
}
|
|
19743
19869
|
let ancestor = target;
|
|
19744
|
-
while (!
|
|
19745
|
-
ancestor =
|
|
19870
|
+
while (!existsSync4(ancestor))
|
|
19871
|
+
ancestor = path7.dirname(ancestor);
|
|
19746
19872
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
19747
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
19873
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
19748
19874
|
throw new Error("path must stay inside repository root");
|
|
19749
19875
|
}
|
|
19750
19876
|
return target;
|
|
@@ -25791,14 +25917,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25791
25917
|
import {
|
|
25792
25918
|
copyFileSync,
|
|
25793
25919
|
cpSync,
|
|
25794
|
-
existsSync as
|
|
25920
|
+
existsSync as existsSync5,
|
|
25795
25921
|
mkdirSync as mkdirSync2,
|
|
25796
25922
|
readFileSync as readFileSync4,
|
|
25797
25923
|
readdirSync as readdirSync3,
|
|
25798
25924
|
writeFileSync as writeFileSync2
|
|
25799
25925
|
} from "node:fs";
|
|
25800
25926
|
import os2 from "node:os";
|
|
25801
|
-
import
|
|
25927
|
+
import path8 from "node:path";
|
|
25802
25928
|
var diagnosticLogger;
|
|
25803
25929
|
var PRESETS = {
|
|
25804
25930
|
gitflow: {
|
|
@@ -25819,7 +25945,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25819
25945
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25820
25946
|
};
|
|
25821
25947
|
};
|
|
25822
|
-
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");
|
|
25823
25949
|
var migratedDir = null;
|
|
25824
25950
|
var migrationFailed = false;
|
|
25825
25951
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25829,12 +25955,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25829
25955
|
migratedDir = dir;
|
|
25830
25956
|
return dir;
|
|
25831
25957
|
}
|
|
25832
|
-
const legacy =
|
|
25833
|
-
if (!
|
|
25958
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25959
|
+
if (!existsSync5(legacy)) {
|
|
25834
25960
|
migratedDir = dir;
|
|
25835
25961
|
return dir;
|
|
25836
25962
|
}
|
|
25837
|
-
if (!migrationFailed &&
|
|
25963
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25838
25964
|
migratedDir = dir;
|
|
25839
25965
|
return dir;
|
|
25840
25966
|
}
|
|
@@ -25842,9 +25968,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25842
25968
|
mkdirSync2(dir, { recursive: true });
|
|
25843
25969
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25844
25970
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25845
|
-
const src =
|
|
25846
|
-
const dest =
|
|
25847
|
-
if (
|
|
25971
|
+
const src = path8.join(legacy, entry.name);
|
|
25972
|
+
const dest = path8.join(dir, entry.name);
|
|
25973
|
+
if (existsSync5(dest))
|
|
25848
25974
|
continue;
|
|
25849
25975
|
try {
|
|
25850
25976
|
if (entry.isDirectory())
|
|
@@ -25917,7 +26043,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25917
26043
|
};
|
|
25918
26044
|
};
|
|
25919
26045
|
var readConfigTyped = (dir) => {
|
|
25920
|
-
const file =
|
|
26046
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25921
26047
|
return parseConfigResult(readSafe(file), file);
|
|
25922
26048
|
};
|
|
25923
26049
|
var readConfig = () => {
|
|
@@ -25953,9 +26079,9 @@ var COMMIT_PRESETS = [
|
|
|
25953
26079
|
|
|
25954
26080
|
// packages/workit-core/src/core/workspaces.ts
|
|
25955
26081
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25956
|
-
import
|
|
26082
|
+
import path9 from "node:path";
|
|
25957
26083
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25958
|
-
const file =
|
|
26084
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25959
26085
|
let raw;
|
|
25960
26086
|
try {
|
|
25961
26087
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26053,10 +26179,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26053
26179
|
|
|
26054
26180
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26055
26181
|
import fs7 from "node:fs";
|
|
26056
|
-
import
|
|
26182
|
+
import path10 from "node:path";
|
|
26057
26183
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26058
26184
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26059
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26185
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26060
26186
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26061
26187
|
var remoteProvider = (cwd) => {
|
|
26062
26188
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26139,8 +26265,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26139
26265
|
}
|
|
26140
26266
|
const prov = cfg[provider] ?? {};
|
|
26141
26267
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26142
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26143
|
-
const tokenPath =
|
|
26268
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26269
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26144
26270
|
let tokenOk = false;
|
|
26145
26271
|
if (fs7.existsSync(tokenPath)) {
|
|
26146
26272
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26149,7 +26275,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26149
26275
|
}
|
|
26150
26276
|
const out = {
|
|
26151
26277
|
ok: true,
|
|
26152
|
-
configPath:
|
|
26278
|
+
configPath: path10.resolve(cfgPath),
|
|
26153
26279
|
provider,
|
|
26154
26280
|
defaultTargetBranch: defaultTarget,
|
|
26155
26281
|
pr: cfg.pr ?? {},
|
|
@@ -26242,18 +26368,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26242
26368
|
|
|
26243
26369
|
// packages/workit-core/src/core/pr-create.ts
|
|
26244
26370
|
import fs8 from "node:fs";
|
|
26245
|
-
import
|
|
26371
|
+
import path11 from "node:path";
|
|
26246
26372
|
function parseGhIssue(value) {
|
|
26247
26373
|
const m = /issues\/(\d+)/.exec(value);
|
|
26248
26374
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26249
26375
|
}
|
|
26250
26376
|
function whichOnPath(tool) {
|
|
26251
26377
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26252
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26378
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26253
26379
|
if (!dir)
|
|
26254
26380
|
continue;
|
|
26255
26381
|
for (const name of names) {
|
|
26256
|
-
const candidate =
|
|
26382
|
+
const candidate = path11.join(dir, name);
|
|
26257
26383
|
try {
|
|
26258
26384
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26259
26385
|
return candidate;
|
|
@@ -26266,8 +26392,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26266
26392
|
|
|
26267
26393
|
// packages/workit-core/src/core/repo-context.ts
|
|
26268
26394
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26269
|
-
import { existsSync as
|
|
26270
|
-
import
|
|
26395
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26396
|
+
import path12 from "node:path";
|
|
26271
26397
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26272
26398
|
try {
|
|
26273
26399
|
gitRevisionParts(value);
|
|
@@ -26401,7 +26527,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26401
26527
|
]) {
|
|
26402
26528
|
const parts = rel.split("/");
|
|
26403
26529
|
const base = parts[parts.length - 1];
|
|
26404
|
-
const dir = parts.length > 1 ?
|
|
26530
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26405
26531
|
const actual = probeCaseInsensitive(dir, base);
|
|
26406
26532
|
if (actual)
|
|
26407
26533
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26432,8 +26558,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26432
26558
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26433
26559
|
};
|
|
26434
26560
|
var packageScripts = (cwd, keys) => {
|
|
26435
|
-
const pkgPath =
|
|
26436
|
-
if (!
|
|
26561
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26562
|
+
if (!existsSync6(pkgPath))
|
|
26437
26563
|
return [];
|
|
26438
26564
|
try {
|
|
26439
26565
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26458,9 +26584,9 @@ var documentationFiles = (cwd) => {
|
|
|
26458
26584
|
if (entry.isDirectory()) {
|
|
26459
26585
|
if (excluded.has(entry.name))
|
|
26460
26586
|
continue;
|
|
26461
|
-
walk(
|
|
26587
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26462
26588
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26463
|
-
matches.push("./" +
|
|
26589
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26464
26590
|
}
|
|
26465
26591
|
}
|
|
26466
26592
|
};
|
|
@@ -26468,7 +26594,7 @@ var documentationFiles = (cwd) => {
|
|
|
26468
26594
|
return matches.sort().slice(0, 200);
|
|
26469
26595
|
};
|
|
26470
26596
|
var readTrimmed = (file, maxLines) => {
|
|
26471
|
-
if (!
|
|
26597
|
+
if (!existsSync6(file))
|
|
26472
26598
|
return "";
|
|
26473
26599
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26474
26600
|
`);
|
|
@@ -26476,7 +26602,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26476
26602
|
`);
|
|
26477
26603
|
};
|
|
26478
26604
|
function prReadyContext(root, range) {
|
|
26479
|
-
const cwd =
|
|
26605
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26480
26606
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26481
26607
|
return invalidRange(cwd);
|
|
26482
26608
|
let stdout = "";
|
|
@@ -26534,7 +26660,7 @@ ${status.stderr}` : "");
|
|
|
26534
26660
|
stdout += `template_path: ${template}
|
|
26535
26661
|
|
|
26536
26662
|
`;
|
|
26537
|
-
stdout += readTrimmed(
|
|
26663
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26538
26664
|
} else {
|
|
26539
26665
|
stdout += `template_path: none
|
|
26540
26666
|
|
|
@@ -26544,7 +26670,7 @@ ${status.stderr}` : "");
|
|
|
26544
26670
|
stdout += `
|
|
26545
26671
|
`;
|
|
26546
26672
|
stdout += printSection("Recent Validation Signals");
|
|
26547
|
-
if (
|
|
26673
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26548
26674
|
stdout += `package.json detected. Common scripts:
|
|
26549
26675
|
`;
|
|
26550
26676
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26553,7 +26679,7 @@ ${status.stderr}` : "");
|
|
|
26553
26679
|
`) + `
|
|
26554
26680
|
`;
|
|
26555
26681
|
}
|
|
26556
|
-
if (
|
|
26682
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26557
26683
|
stdout += `Rust project detected.
|
|
26558
26684
|
`;
|
|
26559
26685
|
}
|
|
@@ -26586,7 +26712,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26586
26712
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26587
26713
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26588
26714
|
function changelogContext(root, range) {
|
|
26589
|
-
const cwd =
|
|
26715
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26590
26716
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26591
26717
|
return invalidRange(cwd);
|
|
26592
26718
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26604,8 +26730,8 @@ function changelogContext(root, range) {
|
|
|
26604
26730
|
stdout += CHANGELOG_RULES + `
|
|
26605
26731
|
`;
|
|
26606
26732
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26607
|
-
const changelogPath =
|
|
26608
|
-
if (
|
|
26733
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26734
|
+
if (existsSync6(changelogPath)) {
|
|
26609
26735
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26610
26736
|
`;
|
|
26611
26737
|
} else {
|
|
@@ -26624,7 +26750,7 @@ function changelogContext(root, range) {
|
|
|
26624
26750
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26625
26751
|
}
|
|
26626
26752
|
function docsRefreshContext(root, range) {
|
|
26627
|
-
const cwd =
|
|
26753
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26628
26754
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26629
26755
|
return invalidRange(cwd);
|
|
26630
26756
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26646,8 +26772,8 @@ function docsRefreshContext(root, range) {
|
|
|
26646
26772
|
`) + `
|
|
26647
26773
|
`;
|
|
26648
26774
|
stdout += printSection("README Preview");
|
|
26649
|
-
const readme =
|
|
26650
|
-
if (
|
|
26775
|
+
const readme = path12.join(cwd, "README.md");
|
|
26776
|
+
if (existsSync6(readme)) {
|
|
26651
26777
|
stdout += readTrimmed(readme, 220) + `
|
|
26652
26778
|
`;
|
|
26653
26779
|
} else {
|
|
@@ -26655,8 +26781,8 @@ function docsRefreshContext(root, range) {
|
|
|
26655
26781
|
`;
|
|
26656
26782
|
}
|
|
26657
26783
|
stdout += printSection("Package Scripts");
|
|
26658
|
-
const pkgPath =
|
|
26659
|
-
if (
|
|
26784
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26785
|
+
if (existsSync6(pkgPath)) {
|
|
26660
26786
|
try {
|
|
26661
26787
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26662
26788
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26669,7 +26795,7 @@ function docsRefreshContext(root, range) {
|
|
|
26669
26795
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26670
26796
|
}
|
|
26671
26797
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26672
|
-
const cwd =
|
|
26798
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26673
26799
|
if (!rangeOrTag) {
|
|
26674
26800
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26675
26801
|
`, exitCode: 1, cwd };
|
|
@@ -26707,7 +26833,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26707
26833
|
`;
|
|
26708
26834
|
stdout += printSection("Existing Release Files");
|
|
26709
26835
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26710
|
-
if (
|
|
26836
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26711
26837
|
stdout += rel + `
|
|
26712
26838
|
`;
|
|
26713
26839
|
}
|
|
@@ -26898,25 +27024,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26898
27024
|
import fs10 from "node:fs";
|
|
26899
27025
|
import path13 from "node:path";
|
|
26900
27026
|
|
|
26901
|
-
// packages/workit-core/src/core/package-root.ts
|
|
26902
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
26903
|
-
import path12 from "node:path";
|
|
26904
|
-
import { fileURLToPath } from "node:url";
|
|
26905
|
-
var packageRoot = () => {
|
|
26906
|
-
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
26907
|
-
while (true) {
|
|
26908
|
-
const parent = path12.dirname(dir);
|
|
26909
|
-
if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
|
|
26910
|
-
return dir;
|
|
26911
|
-
dir = parent;
|
|
26912
|
-
}
|
|
26913
|
-
};
|
|
26914
|
-
var assetRoot = () => {
|
|
26915
|
-
const root = packageRoot();
|
|
26916
|
-
const assets = path12.join(root, "assets");
|
|
26917
|
-
return existsSync6(assets) ? assets : root;
|
|
26918
|
-
};
|
|
26919
|
-
|
|
26920
27027
|
// packages/workit-core/src/core/templates.ts
|
|
26921
27028
|
var repoRoot2 = assetRoot();
|
|
26922
27029
|
|
|
@@ -27420,6 +27527,32 @@ var toolInputSchema = (family) => {
|
|
|
27420
27527
|
const schema = boundedOperationJsonSchema(family);
|
|
27421
27528
|
return { type: "object", ...schema };
|
|
27422
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
|
+
};
|
|
27423
27556
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27424
27557
|
if (result.ok)
|
|
27425
27558
|
return result;
|
|
@@ -27474,13 +27607,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27474
27607
|
].filter((file) => existsSync7(file)));
|
|
27475
27608
|
let staleWarned = false;
|
|
27476
27609
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27477
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27478
|
-
|
|
27479
|
-
|
|
27480
|
-
|
|
27481
|
-
|
|
27482
|
-
}
|
|
27483
|
-
|
|
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
|
+
});
|
|
27484
27620
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27485
27621
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27486
27622
|
uri: `workit://context/${kind}`,
|
|
@@ -27595,7 +27731,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27595
27731
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27596
27732
|
if (!parsed.ok)
|
|
27597
27733
|
return resultForClient(parsed, workspaceRoot);
|
|
27598
|
-
if (context.callerAttested
|
|
27734
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27599
27735
|
return resultForClient({
|
|
27600
27736
|
ok: false,
|
|
27601
27737
|
schemaVersion: 1,
|
|
@@ -27655,6 +27791,13 @@ var codexCapabilities = (host, availability = {}) => {
|
|
|
27655
27791
|
reason: "file writes are host-policy; PreToolUse allows write tools",
|
|
27656
27792
|
refs: [ref(host, "PreToolUse")]
|
|
27657
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
|
+
},
|
|
27658
27801
|
{
|
|
27659
27802
|
name: "native_subagents",
|
|
27660
27803
|
surface: "SubagentStart/SubagentStop",
|