@brainervirus/workit-codex 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +366 -222
- package/dist/workit-hook.js +309 -152
- 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,10 +14419,12 @@ var decisionSchema = object({
|
|
|
14419
14419
|
scope: scopeSchema,
|
|
14420
14420
|
presented: text,
|
|
14421
14421
|
approvedContent: text,
|
|
14422
|
-
|
|
14422
|
+
displayed: text.optional(),
|
|
14423
|
+
contentRefs: array(refSchema),
|
|
14424
|
+
statedChoice: object({ ref: text, text }).strict().optional()
|
|
14423
14425
|
}).strict(),
|
|
14424
14426
|
digest,
|
|
14425
|
-
response: _enum(["approved", "rejected"]),
|
|
14427
|
+
response: _enum(["approved", "rejected", "stated"]),
|
|
14426
14428
|
requirementIds: array(digest),
|
|
14427
14429
|
revoked: object({ at: utc, reason: text }).strict().nullable(),
|
|
14428
14430
|
consumption: object({
|
|
@@ -14497,6 +14499,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
14497
14499
|
path: ["decisionId"]
|
|
14498
14500
|
});
|
|
14499
14501
|
});
|
|
14502
|
+
var runtimeSchema = object({
|
|
14503
|
+
createdWith: text.nullable(),
|
|
14504
|
+
updatedWith: text
|
|
14505
|
+
}).strict();
|
|
14500
14506
|
var taskRecordSchema = object({
|
|
14501
14507
|
schemaVersion: literal(1),
|
|
14502
14508
|
id,
|
|
@@ -14510,6 +14516,8 @@ var taskRecordSchema = object({
|
|
|
14510
14516
|
status: _enum(["active", "paused", "closed"]),
|
|
14511
14517
|
closure: closureSchema.nullable(),
|
|
14512
14518
|
progress: progressSchema,
|
|
14519
|
+
pauseReason: text.nullable().optional(),
|
|
14520
|
+
runtime: runtimeSchema.optional(),
|
|
14513
14521
|
assessments: array(entrySchema(assessmentSchema)),
|
|
14514
14522
|
policy: policySchema.nullable(),
|
|
14515
14523
|
policyChanges: array(policyChangeSchema),
|
|
@@ -14525,6 +14533,7 @@ var workspaceRecordSchema = object({
|
|
|
14525
14533
|
id,
|
|
14526
14534
|
revision,
|
|
14527
14535
|
root: nonEmpty,
|
|
14536
|
+
runtime: runtimeSchema.optional(),
|
|
14528
14537
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
14529
14538
|
}).strict();
|
|
14530
14539
|
var capabilitySchema = object({
|
|
@@ -14552,6 +14561,9 @@ var taskSummarySchema = object({
|
|
|
14552
14561
|
id,
|
|
14553
14562
|
revision,
|
|
14554
14563
|
workspaceRevision: revision,
|
|
14564
|
+
createdAt: utc,
|
|
14565
|
+
updatedAt: utc,
|
|
14566
|
+
runtime: runtimeSchema.nullable(),
|
|
14555
14567
|
objective: text,
|
|
14556
14568
|
status: _enum(["active", "paused", "closed"]),
|
|
14557
14569
|
closure: closureSchema.nullable(),
|
|
@@ -14622,7 +14634,7 @@ var taskOperations = {
|
|
|
14622
14634
|
expectedWorkspaceRevision: revision.optional(),
|
|
14623
14635
|
outcome: outcomeSchema,
|
|
14624
14636
|
summary: text,
|
|
14625
|
-
decisionIds: array(id)
|
|
14637
|
+
decisionIds: array(id).optional()
|
|
14626
14638
|
})
|
|
14627
14639
|
};
|
|
14628
14640
|
var policyOperations = {
|
|
@@ -14714,14 +14726,15 @@ var writerOperations = {
|
|
|
14714
14726
|
...taskId,
|
|
14715
14727
|
expectedRevision: revision.optional(),
|
|
14716
14728
|
expectedWorkspaceRevision: revision.optional(),
|
|
14717
|
-
workerId: nullableId.optional()
|
|
14729
|
+
workerId: nullableId.optional(),
|
|
14730
|
+
reason: text.optional()
|
|
14718
14731
|
}),
|
|
14719
14732
|
release: operation({
|
|
14720
14733
|
action: literal("release"),
|
|
14721
14734
|
...taskId,
|
|
14722
14735
|
expectedRevision: revision.optional(),
|
|
14723
14736
|
expectedWorkspaceRevision: revision.optional(),
|
|
14724
|
-
reason: text
|
|
14737
|
+
reason: text.optional()
|
|
14725
14738
|
})
|
|
14726
14739
|
};
|
|
14727
14740
|
var stateOperations = {
|
|
@@ -14928,7 +14941,26 @@ function requirementId(input) {
|
|
|
14928
14941
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
14929
14942
|
import * as fs4 from "node:fs";
|
|
14930
14943
|
import { hostname } from "node:os";
|
|
14931
|
-
import
|
|
14944
|
+
import path4 from "node:path";
|
|
14945
|
+
|
|
14946
|
+
// packages/workit-core/src/core/package-root.ts
|
|
14947
|
+
import { existsSync } from "node:fs";
|
|
14948
|
+
import path from "node:path";
|
|
14949
|
+
import { fileURLToPath } from "node:url";
|
|
14950
|
+
var packageRoot = () => {
|
|
14951
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
14952
|
+
while (true) {
|
|
14953
|
+
const parent = path.dirname(dir);
|
|
14954
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
14955
|
+
return dir;
|
|
14956
|
+
dir = parent;
|
|
14957
|
+
}
|
|
14958
|
+
};
|
|
14959
|
+
var assetRoot = () => {
|
|
14960
|
+
const root = packageRoot();
|
|
14961
|
+
const assets = path.join(root, "assets");
|
|
14962
|
+
return existsSync(assets) ? assets : root;
|
|
14963
|
+
};
|
|
14932
14964
|
|
|
14933
14965
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
14934
14966
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -14979,7 +15011,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
14979
15011
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
14980
15012
|
import { randomBytes } from "node:crypto";
|
|
14981
15013
|
import fsSync from "node:fs";
|
|
14982
|
-
import
|
|
15014
|
+
import path2 from "node:path";
|
|
14983
15015
|
|
|
14984
15016
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
14985
15017
|
import fs from "node:fs";
|
|
@@ -15069,13 +15101,13 @@ ${ownershipToken}
|
|
|
15069
15101
|
return { raw, ownershipToken };
|
|
15070
15102
|
}
|
|
15071
15103
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
15072
|
-
const resolved =
|
|
15073
|
-
const lexicalRelative =
|
|
15074
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
15075
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
15104
|
+
const resolved = path2.resolve(lockPath);
|
|
15105
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
15106
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
15107
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
15076
15108
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
15077
15109
|
}
|
|
15078
|
-
return relative.split(
|
|
15110
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
15079
15111
|
}
|
|
15080
15112
|
function parseSidecarLockPayload(raw, parser) {
|
|
15081
15113
|
if (parser) {
|
|
@@ -15222,7 +15254,7 @@ function getFsSafeLockConfig() {
|
|
|
15222
15254
|
|
|
15223
15255
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
15224
15256
|
import fs3 from "node:fs";
|
|
15225
|
-
import
|
|
15257
|
+
import path3 from "node:path";
|
|
15226
15258
|
|
|
15227
15259
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
15228
15260
|
function sleepSync(ms) {
|
|
@@ -15313,26 +15345,26 @@ function canonicalLockParentSync(parent) {
|
|
|
15313
15345
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
15314
15346
|
}
|
|
15315
15347
|
function normalizeTargetPath(targetPath) {
|
|
15316
|
-
const resolved =
|
|
15317
|
-
fs3.mkdirSync(
|
|
15348
|
+
const resolved = path3.resolve(targetPath);
|
|
15349
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
15318
15350
|
try {
|
|
15319
|
-
return
|
|
15351
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
15320
15352
|
} catch {
|
|
15321
15353
|
return resolved;
|
|
15322
15354
|
}
|
|
15323
15355
|
}
|
|
15324
15356
|
function boundedLockPath(lockPath, lockRoot) {
|
|
15325
|
-
const resolved =
|
|
15357
|
+
const resolved = path3.resolve(lockPath);
|
|
15326
15358
|
if (!lockRoot)
|
|
15327
15359
|
return resolved;
|
|
15328
15360
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
15329
|
-
const parent =
|
|
15361
|
+
const parent = path3.dirname(resolved);
|
|
15330
15362
|
const parentReal = canonicalLockParentSync(parent);
|
|
15331
|
-
const parentRelative =
|
|
15332
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
15363
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
15364
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
15333
15365
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
15334
15366
|
}
|
|
15335
|
-
return
|
|
15367
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
15336
15368
|
}
|
|
15337
15369
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
15338
15370
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -15545,6 +15577,33 @@ var metadataLockSchema = object({
|
|
|
15545
15577
|
nonce: string2().min(1)
|
|
15546
15578
|
}).strict();
|
|
15547
15579
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
15580
|
+
var cachedRuntimeVersion = null;
|
|
15581
|
+
var runtimeVersion = () => {
|
|
15582
|
+
if (cachedRuntimeVersion === null) {
|
|
15583
|
+
try {
|
|
15584
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
15585
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
15586
|
+
} catch {
|
|
15587
|
+
cachedRuntimeVersion = "0.0.0";
|
|
15588
|
+
}
|
|
15589
|
+
}
|
|
15590
|
+
return cachedRuntimeVersion;
|
|
15591
|
+
};
|
|
15592
|
+
var stampNew = (record) => ({
|
|
15593
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
15594
|
+
updatedWith: runtimeVersion()
|
|
15595
|
+
});
|
|
15596
|
+
var isNewerVersion = (candidate, current) => {
|
|
15597
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
15598
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
15599
|
+
for (let index = 0;index < 3; index += 1) {
|
|
15600
|
+
const left = next[index] ?? 0;
|
|
15601
|
+
const right = now[index] ?? 0;
|
|
15602
|
+
if (left !== right)
|
|
15603
|
+
return left > right;
|
|
15604
|
+
}
|
|
15605
|
+
return false;
|
|
15606
|
+
};
|
|
15548
15607
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
15549
15608
|
`;
|
|
15550
15609
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -15571,12 +15630,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
15571
15630
|
class TaskStore {
|
|
15572
15631
|
root;
|
|
15573
15632
|
constructor(root) {
|
|
15574
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
15633
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
15575
15634
|
}
|
|
15576
15635
|
readTask(taskId) {
|
|
15577
15636
|
if (!validId(taskId))
|
|
15578
15637
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
15579
|
-
const result = this.readRecord(
|
|
15638
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
15580
15639
|
if (!result.exists)
|
|
15581
15640
|
return failure("not_found", "task not found", { taskId });
|
|
15582
15641
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -15610,7 +15669,7 @@ class TaskStore {
|
|
|
15610
15669
|
});
|
|
15611
15670
|
const tasks = [];
|
|
15612
15671
|
for (const name of names.sort()) {
|
|
15613
|
-
const item = this.readRecord(
|
|
15672
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
15614
15673
|
if (!item.exists)
|
|
15615
15674
|
continue;
|
|
15616
15675
|
if (!item.result.ok)
|
|
@@ -15644,7 +15703,7 @@ class TaskStore {
|
|
|
15644
15703
|
if (!current.ok)
|
|
15645
15704
|
return current;
|
|
15646
15705
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
15647
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
15706
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15648
15707
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
15649
15708
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
15650
15709
|
});
|
|
@@ -15656,11 +15715,17 @@ class TaskStore {
|
|
|
15656
15715
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
15657
15716
|
if (current.data && !previousWorkspaceBytes)
|
|
15658
15717
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
15659
|
-
const workspace = current.data ? {
|
|
15718
|
+
const workspace = current.data ? {
|
|
15719
|
+
...current.data,
|
|
15720
|
+
revision: newRevision(),
|
|
15721
|
+
root: this.root,
|
|
15722
|
+
runtime: stampNew(current.data)
|
|
15723
|
+
} : {
|
|
15660
15724
|
schemaVersion: SCHEMA_VERSION,
|
|
15661
15725
|
id: newId(),
|
|
15662
15726
|
revision: newRevision(),
|
|
15663
15727
|
root: this.root,
|
|
15728
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15664
15729
|
writer: null
|
|
15665
15730
|
};
|
|
15666
15731
|
const timestamp = value.now ?? now();
|
|
@@ -15682,6 +15747,7 @@ class TaskStore {
|
|
|
15682
15747
|
status: "active",
|
|
15683
15748
|
closure: null,
|
|
15684
15749
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
15750
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15685
15751
|
assessments: [],
|
|
15686
15752
|
policy: null,
|
|
15687
15753
|
policyChanges: [],
|
|
@@ -15712,7 +15778,7 @@ class TaskStore {
|
|
|
15712
15778
|
if (!current.ok)
|
|
15713
15779
|
return current;
|
|
15714
15780
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
15715
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
15781
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
15716
15782
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
15717
15783
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
15718
15784
|
});
|
|
@@ -15720,11 +15786,17 @@ class TaskStore {
|
|
|
15720
15786
|
if (current.data && !previousWorkspaceBytes)
|
|
15721
15787
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
15722
15788
|
const timestamp = input.now ?? now();
|
|
15723
|
-
const workspace = current.data ? {
|
|
15789
|
+
const workspace = current.data ? {
|
|
15790
|
+
...current.data,
|
|
15791
|
+
revision: newRevision(),
|
|
15792
|
+
root: this.root,
|
|
15793
|
+
runtime: stampNew(current.data)
|
|
15794
|
+
} : {
|
|
15724
15795
|
schemaVersion: SCHEMA_VERSION,
|
|
15725
15796
|
id: input.workspaceId ?? newId(),
|
|
15726
15797
|
revision: newRevision(),
|
|
15727
15798
|
root: this.root,
|
|
15799
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
15728
15800
|
writer: null
|
|
15729
15801
|
};
|
|
15730
15802
|
const task = {
|
|
@@ -15732,7 +15804,11 @@ class TaskStore {
|
|
|
15732
15804
|
workspaceId: workspace.id,
|
|
15733
15805
|
revision: newRevision(),
|
|
15734
15806
|
createdAt: timestamp,
|
|
15735
|
-
updatedAt: timestamp
|
|
15807
|
+
updatedAt: timestamp,
|
|
15808
|
+
runtime: {
|
|
15809
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
15810
|
+
updatedWith: runtimeVersion()
|
|
15811
|
+
}
|
|
15736
15812
|
};
|
|
15737
15813
|
const validTask = taskRecordSchema.safeParse(task);
|
|
15738
15814
|
if (!validTask.success)
|
|
@@ -15781,7 +15857,8 @@ class TaskStore {
|
|
|
15781
15857
|
workspaceId: current.data.workspaceId,
|
|
15782
15858
|
createdAt: current.data.createdAt,
|
|
15783
15859
|
revision: context.revision,
|
|
15784
|
-
updatedAt: context.now
|
|
15860
|
+
updatedAt: context.now,
|
|
15861
|
+
runtime: stampNew(current.data)
|
|
15785
15862
|
};
|
|
15786
15863
|
const valid = taskRecordSchema.safeParse(record);
|
|
15787
15864
|
if (!valid.success)
|
|
@@ -15815,7 +15892,8 @@ class TaskStore {
|
|
|
15815
15892
|
...changed.data,
|
|
15816
15893
|
id: current.data.id,
|
|
15817
15894
|
root: this.root,
|
|
15818
|
-
revision: context.revision
|
|
15895
|
+
revision: context.revision,
|
|
15896
|
+
runtime: stampNew(current.data)
|
|
15819
15897
|
};
|
|
15820
15898
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
15821
15899
|
if (!valid.success)
|
|
@@ -15858,7 +15936,8 @@ class TaskStore {
|
|
|
15858
15936
|
...changedWorkspace.data,
|
|
15859
15937
|
id: workspace.data.id,
|
|
15860
15938
|
root: this.root,
|
|
15861
|
-
revision: workspaceContext.revision
|
|
15939
|
+
revision: workspaceContext.revision,
|
|
15940
|
+
runtime: stampNew(workspace.data)
|
|
15862
15941
|
});
|
|
15863
15942
|
if (!nextWorkspace.success)
|
|
15864
15943
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -15883,7 +15962,8 @@ class TaskStore {
|
|
|
15883
15962
|
workspaceId: task.data.workspaceId,
|
|
15884
15963
|
createdAt: task.data.createdAt,
|
|
15885
15964
|
revision: taskContext.revision,
|
|
15886
|
-
updatedAt: taskContext.now
|
|
15965
|
+
updatedAt: taskContext.now,
|
|
15966
|
+
runtime: stampNew(task.data)
|
|
15887
15967
|
});
|
|
15888
15968
|
if (!nextTask.success) {
|
|
15889
15969
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -15910,7 +15990,7 @@ class TaskStore {
|
|
|
15910
15990
|
if (match)
|
|
15911
15991
|
candidates.push({
|
|
15912
15992
|
target: match[1],
|
|
15913
|
-
path:
|
|
15993
|
+
path: path4.join(this.recoveryDir, name),
|
|
15914
15994
|
digest: match[3]
|
|
15915
15995
|
});
|
|
15916
15996
|
}
|
|
@@ -16103,7 +16183,8 @@ class TaskStore {
|
|
|
16103
16183
|
const value = {
|
|
16104
16184
|
...workspace,
|
|
16105
16185
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
16106
|
-
revision: newRevision()
|
|
16186
|
+
revision: newRevision(),
|
|
16187
|
+
runtime: stampNew(workspace)
|
|
16107
16188
|
};
|
|
16108
16189
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
16109
16190
|
}
|
|
@@ -16181,7 +16262,7 @@ class TaskStore {
|
|
|
16181
16262
|
}
|
|
16182
16263
|
fs4.renameSync(temporary, file);
|
|
16183
16264
|
temporary = undefined;
|
|
16184
|
-
this.fsyncDirectory(
|
|
16265
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
16185
16266
|
return success(null, null, value);
|
|
16186
16267
|
} catch (error) {
|
|
16187
16268
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -16195,9 +16276,9 @@ class TaskStore {
|
|
|
16195
16276
|
}
|
|
16196
16277
|
}
|
|
16197
16278
|
saveRecovery(file, bytes) {
|
|
16198
|
-
const target =
|
|
16199
|
-
const id = target === "task" ?
|
|
16200
|
-
const destination =
|
|
16279
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
16280
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
16281
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
16201
16282
|
if (fs4.existsSync(destination)) {
|
|
16202
16283
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
16203
16284
|
return;
|
|
@@ -16275,7 +16356,7 @@ class TaskStore {
|
|
|
16275
16356
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
16276
16357
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
16277
16358
|
return null;
|
|
16278
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
16359
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
16279
16360
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
16280
16361
|
return null;
|
|
16281
16362
|
ids.add(parsed.data.workspaceId);
|
|
@@ -16317,7 +16398,12 @@ class TaskStore {
|
|
|
16317
16398
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
16318
16399
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
16319
16400
|
const parsed = schema.safeParse(value);
|
|
16320
|
-
|
|
16401
|
+
if (parsed.success)
|
|
16402
|
+
return success(null, null, parsed.data);
|
|
16403
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
16404
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
16405
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
16406
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
16321
16407
|
}
|
|
16322
16408
|
processStart(pid) {
|
|
16323
16409
|
try {
|
|
@@ -16327,43 +16413,98 @@ class TaskStore {
|
|
|
16327
16413
|
}
|
|
16328
16414
|
}
|
|
16329
16415
|
conflict(expected, actual) {
|
|
16330
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
16416
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
16331
16417
|
expectedRevision: expected,
|
|
16332
16418
|
actualRevision: actual
|
|
16333
16419
|
});
|
|
16334
16420
|
}
|
|
16335
16421
|
taskPath(taskId) {
|
|
16336
|
-
return
|
|
16422
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
16337
16423
|
}
|
|
16338
16424
|
get workitDir() {
|
|
16339
|
-
return
|
|
16425
|
+
return path4.join(this.root, ".workit");
|
|
16340
16426
|
}
|
|
16341
16427
|
get tasksDir() {
|
|
16342
|
-
return
|
|
16428
|
+
return path4.join(this.workitDir, "tasks");
|
|
16343
16429
|
}
|
|
16344
16430
|
get recoveryDir() {
|
|
16345
|
-
return
|
|
16431
|
+
return path4.join(this.workitDir, "recovery");
|
|
16346
16432
|
}
|
|
16347
16433
|
get workspacePath() {
|
|
16348
|
-
return
|
|
16434
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
16349
16435
|
}
|
|
16350
16436
|
get lockPath() {
|
|
16351
|
-
return
|
|
16437
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
16352
16438
|
}
|
|
16353
16439
|
get gitignorePath() {
|
|
16354
|
-
return
|
|
16440
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
16355
16441
|
}
|
|
16356
16442
|
}
|
|
16443
|
+
// packages/workit-core/src/core/workers.ts
|
|
16444
|
+
var same = (left, right) => {
|
|
16445
|
+
try {
|
|
16446
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
16447
|
+
} catch {
|
|
16448
|
+
return false;
|
|
16449
|
+
}
|
|
16450
|
+
};
|
|
16451
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
16452
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
16453
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
16454
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
16455
|
+
const code = char.charCodeAt(0);
|
|
16456
|
+
return code < 32 || code === 127;
|
|
16457
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
16458
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
16459
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
16460
|
+
function assertProductWriteAllowed(input) {
|
|
16461
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
16462
|
+
return failure("invalid_input", "invalid product write path");
|
|
16463
|
+
if (!input.store)
|
|
16464
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
16465
|
+
const task = input.store.readTask(input.task.id);
|
|
16466
|
+
if (!task.ok)
|
|
16467
|
+
return task;
|
|
16468
|
+
const workspace = input.store.readWorkspace();
|
|
16469
|
+
if (!workspace.ok)
|
|
16470
|
+
return workspace;
|
|
16471
|
+
if (!workspace.data)
|
|
16472
|
+
return failure("not_found", "workspace not found");
|
|
16473
|
+
const workspaceRecord = workspace.data;
|
|
16474
|
+
if (task.data.status !== "active")
|
|
16475
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
16476
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
16477
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
16478
|
+
const writer = workspaceRecord.writer;
|
|
16479
|
+
if (!writer)
|
|
16480
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
16481
|
+
if (writer.state === "uncertain")
|
|
16482
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
16483
|
+
owner: writer.owner
|
|
16484
|
+
});
|
|
16485
|
+
const caller = callerValue(input.caller);
|
|
16486
|
+
const workerId = workerIdOf(input.caller);
|
|
16487
|
+
const owner = writer.owner;
|
|
16488
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
16489
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
16490
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
16491
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
16492
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
16493
|
+
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))))
|
|
16494
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
16495
|
+
return success(null, workspaceRecord.revision, owner);
|
|
16496
|
+
}
|
|
16497
|
+
|
|
16357
16498
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16358
16499
|
import { createHash as createHash4 } from "node:crypto";
|
|
16359
16500
|
import * as fs6 from "node:fs";
|
|
16360
16501
|
import { spawnSync } from "node:child_process";
|
|
16361
|
-
import
|
|
16502
|
+
import path6 from "node:path";
|
|
16362
16503
|
|
|
16363
16504
|
// packages/workit-core/src/core/authority.ts
|
|
16364
16505
|
import { createHash as createHash3 } from "node:crypto";
|
|
16365
16506
|
import * as fs5 from "node:fs";
|
|
16366
|
-
import
|
|
16507
|
+
import path5 from "node:path";
|
|
16367
16508
|
var verifiedAuthorities = new WeakMap;
|
|
16368
16509
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
16369
16510
|
const token = {};
|
|
@@ -16490,20 +16631,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
16490
16631
|
continue;
|
|
16491
16632
|
if (!reference.digest)
|
|
16492
16633
|
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"
|
|
16634
|
+
const target = path5.resolve(root, reference.path);
|
|
16635
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
16636
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
16637
|
+
fields: [
|
|
16638
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
16639
|
+
]
|
|
16640
|
+
});
|
|
16496
16641
|
try {
|
|
16497
|
-
const relative =
|
|
16642
|
+
const relative = path5.relative(root, target);
|
|
16498
16643
|
let current = root;
|
|
16499
|
-
for (const segment of relative.split(
|
|
16644
|
+
for (const segment of relative.split(path5.sep)) {
|
|
16500
16645
|
if (!segment)
|
|
16501
16646
|
continue;
|
|
16502
|
-
current =
|
|
16647
|
+
current = path5.join(current, segment);
|
|
16503
16648
|
const stat = fs5.lstatSync(current);
|
|
16504
16649
|
const real = fs5.realpathSync(current);
|
|
16505
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
16506
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
16650
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
16651
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
16652
|
+
fields: [
|
|
16653
|
+
{
|
|
16654
|
+
path: "contentRefs.path",
|
|
16655
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
16656
|
+
}
|
|
16657
|
+
]
|
|
16658
|
+
});
|
|
16507
16659
|
if (stat.isSymbolicLink())
|
|
16508
16660
|
return failure("invalid_input", "document reference uses a symlink");
|
|
16509
16661
|
if (current === target && !stat.isFile())
|
|
@@ -16850,14 +17002,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
16850
17002
|
|
|
16851
17003
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
16852
17004
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
16853
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
16854
|
-
var relative = (root, candidate) =>
|
|
17005
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
17006
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
16855
17007
|
var canonicalPath = (value) => {
|
|
16856
17008
|
if (typeof value !== "string" || !value)
|
|
16857
17009
|
return null;
|
|
16858
17010
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
16859
17011
|
return null;
|
|
16860
|
-
const normalized =
|
|
17012
|
+
const normalized = path6.posix.normalize(value);
|
|
16861
17013
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
16862
17014
|
return null;
|
|
16863
17015
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -16905,14 +17057,14 @@ var gitPaths = (root) => {
|
|
|
16905
17057
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
16906
17058
|
};
|
|
16907
17059
|
}
|
|
16908
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
17060
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
16909
17061
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
16910
17062
|
if (stagedDeleted.status !== 0) {
|
|
16911
17063
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
16912
17064
|
if (!/not a git repository/i.test(stderr))
|
|
16913
17065
|
return { paths, uncertain: true, git: true };
|
|
16914
17066
|
} else if (stagedDeleted.stdout) {
|
|
16915
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
17067
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
16916
17068
|
}
|
|
16917
17069
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
16918
17070
|
};
|
|
@@ -16927,7 +17079,7 @@ var walk = (root, directory, output) => {
|
|
|
16927
17079
|
for (const name of names) {
|
|
16928
17080
|
if (name === ".git" || name === ".workit")
|
|
16929
17081
|
continue;
|
|
16930
|
-
const target =
|
|
17082
|
+
const target = path6.join(directory, name);
|
|
16931
17083
|
let stat;
|
|
16932
17084
|
try {
|
|
16933
17085
|
stat = fs6.lstatSync(target);
|
|
@@ -16945,17 +17097,17 @@ var walk = (root, directory, output) => {
|
|
|
16945
17097
|
var scopeRoots = (root, scope) => {
|
|
16946
17098
|
const roots = [];
|
|
16947
17099
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
16948
|
-
const target =
|
|
17100
|
+
const target = path6.resolve(root, item);
|
|
16949
17101
|
if (!inside(root, target))
|
|
16950
|
-
return failure("invalid_input",
|
|
17102
|
+
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
17103
|
let ancestor = target;
|
|
16952
17104
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
16953
|
-
ancestor =
|
|
17105
|
+
ancestor = path6.dirname(ancestor);
|
|
16954
17106
|
try {
|
|
16955
17107
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
16956
|
-
return failure("invalid_input",
|
|
17108
|
+
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
17109
|
} catch {
|
|
16958
|
-
return failure("invalid_input",
|
|
17110
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
16959
17111
|
}
|
|
16960
17112
|
roots.push(target);
|
|
16961
17113
|
}
|
|
@@ -16970,7 +17122,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
16970
17122
|
}
|
|
16971
17123
|
const normalizedScope = canonicalScope(scope);
|
|
16972
17124
|
if (!normalizedScope)
|
|
16973
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
17125
|
+
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
17126
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
16975
17127
|
if (!roots.ok)
|
|
16976
17128
|
return roots;
|
|
@@ -17002,7 +17154,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
17002
17154
|
for (const item of [...names].sort()) {
|
|
17003
17155
|
if (!scopeMatches(item, normalizedScope))
|
|
17004
17156
|
continue;
|
|
17005
|
-
const target =
|
|
17157
|
+
const target = path6.join(checkout, item);
|
|
17006
17158
|
try {
|
|
17007
17159
|
const stat = fs6.lstatSync(target);
|
|
17008
17160
|
if (stat.isSymbolicLink()) {
|
|
@@ -17124,7 +17276,7 @@ function evaluateEvidence(task, candidate) {
|
|
|
17124
17276
|
}
|
|
17125
17277
|
var scopeCovers2 = scopeCovers;
|
|
17126
17278
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
17127
|
-
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
17279
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
17128
17280
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
17129
17281
|
if (kind !== "passed")
|
|
17130
17282
|
return false;
|
|
@@ -17159,7 +17311,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
17159
17311
|
return true;
|
|
17160
17312
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
17161
17313
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
17162
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
17314
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
17163
17315
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
17164
17316
|
});
|
|
17165
17317
|
if (passed.length) {
|
|
@@ -17200,7 +17352,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
17200
17352
|
status: "satisfied",
|
|
17201
17353
|
evidenceIds: [],
|
|
17202
17354
|
decisionIds: decisions.map((decision) => decision.id),
|
|
17203
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
17355
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
17204
17356
|
};
|
|
17205
17357
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
17206
17358
|
if (limitations.length)
|
|
@@ -17255,7 +17407,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
17255
17407
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
17256
17408
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
17257
17409
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
17258
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
17410
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
17411
|
+
requirements: blocking.map((item) => {
|
|
17412
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
17413
|
+
return {
|
|
17414
|
+
requirementId: item.requirementId,
|
|
17415
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
17416
|
+
reason: item.reason,
|
|
17417
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
17418
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
17419
|
+
};
|
|
17420
|
+
})
|
|
17259
17421
|
});
|
|
17260
17422
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
17261
17423
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -17291,7 +17453,7 @@ function reconcileResume(view, observations = []) {
|
|
|
17291
17453
|
return candidate;
|
|
17292
17454
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
17293
17455
|
const blockers = [...view.task.progress.blockers];
|
|
17294
|
-
if (view.task.workers.some((entry) =>
|
|
17456
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
17295
17457
|
blockers.push({
|
|
17296
17458
|
reason: "worker state requires reconciliation",
|
|
17297
17459
|
dependentAction: "resume",
|
|
@@ -17324,7 +17486,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
17324
17486
|
}).strict(),
|
|
17325
17487
|
object({
|
|
17326
17488
|
operation: literal("git.commit"),
|
|
17327
|
-
payload: object({
|
|
17489
|
+
payload: object({
|
|
17490
|
+
message: string2().min(1).optional(),
|
|
17491
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
17492
|
+
plan_branch: string2().min(1).optional()
|
|
17493
|
+
}).strict()
|
|
17328
17494
|
}).strict(),
|
|
17329
17495
|
object({
|
|
17330
17496
|
operation: literal("git.push"),
|
|
@@ -17559,7 +17725,7 @@ function resolveRequirements(input) {
|
|
|
17559
17725
|
].join(", ")}`,
|
|
17560
17726
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
17561
17727
|
before: "dependent_action",
|
|
17562
|
-
dependentAction:
|
|
17728
|
+
dependentAction: null,
|
|
17563
17729
|
acceptanceAllowed: false
|
|
17564
17730
|
}));
|
|
17565
17731
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -17570,7 +17736,7 @@ function resolveRequirements(input) {
|
|
|
17570
17736
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
17571
17737
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
17572
17738
|
before: "dependent_action",
|
|
17573
|
-
dependentAction:
|
|
17739
|
+
dependentAction: null,
|
|
17574
17740
|
acceptanceAllowed: false
|
|
17575
17741
|
}));
|
|
17576
17742
|
if (signals.behaviorChange.value === true)
|
|
@@ -17728,59 +17894,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
17728
17894
|
return parsed.data;
|
|
17729
17895
|
}
|
|
17730
17896
|
|
|
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
17897
|
// packages/workit-core/src/core/task-engine.ts
|
|
17785
17898
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
17786
17899
|
kind,
|
|
@@ -17911,15 +18024,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
17911
18024
|
return failure("invalid_transition", "worker has not started");
|
|
17912
18025
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
17913
18026
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
18027
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
17914
18028
|
const nextEntry = {
|
|
17915
18029
|
...entry,
|
|
17916
18030
|
recordedAt: input.now ?? entry.recordedAt,
|
|
17917
18031
|
provenance: authority.provenance,
|
|
17918
|
-
data: {
|
|
18032
|
+
data: {
|
|
18033
|
+
...entry.data,
|
|
18034
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
18035
|
+
session: input.session
|
|
18036
|
+
}
|
|
17919
18037
|
};
|
|
17920
18038
|
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
18039
|
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 ===
|
|
18040
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
17923
18041
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
17924
18042
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
17925
18043
|
taskId: task.data.id,
|
|
@@ -18289,7 +18407,7 @@ class WorkitCore {
|
|
|
18289
18407
|
activeWorkerBlocker(task, workspace) {
|
|
18290
18408
|
if (workspace.writer)
|
|
18291
18409
|
return failure("recovery_required", "writer ownership must be released first");
|
|
18292
|
-
if (task.workers.some((entry) =>
|
|
18410
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
18293
18411
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
18294
18412
|
return null;
|
|
18295
18413
|
}
|
|
@@ -18526,11 +18644,6 @@ class WorkitCore {
|
|
|
18526
18644
|
if (helper && !helper.ok)
|
|
18527
18645
|
return helper;
|
|
18528
18646
|
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
18647
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
18535
18648
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
18536
18649
|
if (evidence.kind === "review") {
|
|
@@ -18547,6 +18660,11 @@ class WorkitCore {
|
|
|
18547
18660
|
evidence.beforeCandidateId ??= null;
|
|
18548
18661
|
evidence.candidateId ??= null;
|
|
18549
18662
|
}
|
|
18663
|
+
if (helper?.ok) {
|
|
18664
|
+
const assignment = helper.data.data.assignment;
|
|
18665
|
+
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))
|
|
18666
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
18667
|
+
}
|
|
18550
18668
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
18551
18669
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
18552
18670
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -18600,7 +18718,12 @@ class WorkitCore {
|
|
|
18600
18718
|
const input = parsed.data;
|
|
18601
18719
|
if (nativeRequired && input.action !== "record")
|
|
18602
18720
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
18603
|
-
|
|
18721
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
18722
|
+
if (stated && input.purpose === "action")
|
|
18723
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
18724
|
+
if (stated && !input.binding?.statedChoice)
|
|
18725
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
18726
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
18604
18727
|
return failure("permission_denied", "native decision observation is required");
|
|
18605
18728
|
const task = this.store.readTask(input.taskId);
|
|
18606
18729
|
if (!task.ok)
|
|
@@ -18633,7 +18756,7 @@ class WorkitCore {
|
|
|
18633
18756
|
consumption: null
|
|
18634
18757
|
};
|
|
18635
18758
|
const data = { ...base, digest: decisionDigest(base) };
|
|
18636
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
18759
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
18637
18760
|
observation: nativeObservation,
|
|
18638
18761
|
expected: {
|
|
18639
18762
|
taskId: task.data.id,
|
|
@@ -19442,6 +19565,9 @@ class WorkitCore {
|
|
|
19442
19565
|
id: task.id,
|
|
19443
19566
|
revision: task.revision,
|
|
19444
19567
|
workspaceRevision: workspace.data.revision,
|
|
19568
|
+
createdAt: task.createdAt,
|
|
19569
|
+
updatedAt: task.updatedAt,
|
|
19570
|
+
runtime: task.runtime ?? null,
|
|
19445
19571
|
objective: task.intent.data.objective,
|
|
19446
19572
|
status: task.status,
|
|
19447
19573
|
closure: task.closure,
|
|
@@ -19527,7 +19653,7 @@ class WorkitCore {
|
|
|
19527
19653
|
const base = reconcileResume(effectiveView);
|
|
19528
19654
|
if (!base.ok)
|
|
19529
19655
|
return base;
|
|
19530
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
19656
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
19531
19657
|
const blockers = [...base.data.blockers];
|
|
19532
19658
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
19533
19659
|
blockers.push({
|
|
@@ -19594,7 +19720,8 @@ class WorkitCore {
|
|
|
19594
19720
|
...current,
|
|
19595
19721
|
status,
|
|
19596
19722
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
19597
|
-
progress:
|
|
19723
|
+
progress: current.progress,
|
|
19724
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
19598
19725
|
})
|
|
19599
19726
|
});
|
|
19600
19727
|
if (!changed.ok)
|
|
@@ -19630,7 +19757,7 @@ class WorkitCore {
|
|
|
19630
19757
|
return workspace;
|
|
19631
19758
|
if (!workspace.data)
|
|
19632
19759
|
return failure("not_found", "workspace not found");
|
|
19633
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
19760
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
19634
19761
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
19635
19762
|
this.fillRevisions(input, task.data, workspace.data);
|
|
19636
19763
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -19741,15 +19868,15 @@ function resolveGitRevision(root, value) {
|
|
|
19741
19868
|
}
|
|
19742
19869
|
function resolveInside(root, candidate) {
|
|
19743
19870
|
const base = realpathSync5(root);
|
|
19744
|
-
const target =
|
|
19745
|
-
if (target !== base && !target.startsWith(base +
|
|
19871
|
+
const target = path7.resolve(base, candidate);
|
|
19872
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
19746
19873
|
throw new Error("path must stay inside repository root");
|
|
19747
19874
|
}
|
|
19748
19875
|
let ancestor = target;
|
|
19749
|
-
while (!
|
|
19750
|
-
ancestor =
|
|
19876
|
+
while (!existsSync4(ancestor))
|
|
19877
|
+
ancestor = path7.dirname(ancestor);
|
|
19751
19878
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
19752
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
19879
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
19753
19880
|
throw new Error("path must stay inside repository root");
|
|
19754
19881
|
}
|
|
19755
19882
|
return target;
|
|
@@ -25796,14 +25923,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25796
25923
|
import {
|
|
25797
25924
|
copyFileSync,
|
|
25798
25925
|
cpSync,
|
|
25799
|
-
existsSync as
|
|
25926
|
+
existsSync as existsSync5,
|
|
25800
25927
|
mkdirSync as mkdirSync2,
|
|
25801
25928
|
readFileSync as readFileSync4,
|
|
25802
25929
|
readdirSync as readdirSync3,
|
|
25803
25930
|
writeFileSync as writeFileSync2
|
|
25804
25931
|
} from "node:fs";
|
|
25805
25932
|
import os2 from "node:os";
|
|
25806
|
-
import
|
|
25933
|
+
import path8 from "node:path";
|
|
25807
25934
|
var diagnosticLogger;
|
|
25808
25935
|
var PRESETS = {
|
|
25809
25936
|
gitflow: {
|
|
@@ -25824,7 +25951,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25824
25951
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25825
25952
|
};
|
|
25826
25953
|
};
|
|
25827
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
25954
|
+
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
25955
|
var migratedDir = null;
|
|
25829
25956
|
var migrationFailed = false;
|
|
25830
25957
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25834,12 +25961,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25834
25961
|
migratedDir = dir;
|
|
25835
25962
|
return dir;
|
|
25836
25963
|
}
|
|
25837
|
-
const legacy =
|
|
25838
|
-
if (!
|
|
25964
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25965
|
+
if (!existsSync5(legacy)) {
|
|
25839
25966
|
migratedDir = dir;
|
|
25840
25967
|
return dir;
|
|
25841
25968
|
}
|
|
25842
|
-
if (!migrationFailed &&
|
|
25969
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25843
25970
|
migratedDir = dir;
|
|
25844
25971
|
return dir;
|
|
25845
25972
|
}
|
|
@@ -25847,9 +25974,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25847
25974
|
mkdirSync2(dir, { recursive: true });
|
|
25848
25975
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25849
25976
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25850
|
-
const src =
|
|
25851
|
-
const dest =
|
|
25852
|
-
if (
|
|
25977
|
+
const src = path8.join(legacy, entry.name);
|
|
25978
|
+
const dest = path8.join(dir, entry.name);
|
|
25979
|
+
if (existsSync5(dest))
|
|
25853
25980
|
continue;
|
|
25854
25981
|
try {
|
|
25855
25982
|
if (entry.isDirectory())
|
|
@@ -25922,7 +26049,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25922
26049
|
};
|
|
25923
26050
|
};
|
|
25924
26051
|
var readConfigTyped = (dir) => {
|
|
25925
|
-
const file =
|
|
26052
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25926
26053
|
return parseConfigResult(readSafe(file), file);
|
|
25927
26054
|
};
|
|
25928
26055
|
var readConfig = () => {
|
|
@@ -25958,9 +26085,9 @@ var COMMIT_PRESETS = [
|
|
|
25958
26085
|
|
|
25959
26086
|
// packages/workit-core/src/core/workspaces.ts
|
|
25960
26087
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25961
|
-
import
|
|
26088
|
+
import path9 from "node:path";
|
|
25962
26089
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25963
|
-
const file =
|
|
26090
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25964
26091
|
let raw;
|
|
25965
26092
|
try {
|
|
25966
26093
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26058,10 +26185,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26058
26185
|
|
|
26059
26186
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26060
26187
|
import fs7 from "node:fs";
|
|
26061
|
-
import
|
|
26188
|
+
import path10 from "node:path";
|
|
26062
26189
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26063
26190
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26064
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26191
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26065
26192
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26066
26193
|
var remoteProvider = (cwd) => {
|
|
26067
26194
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26144,8 +26271,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26144
26271
|
}
|
|
26145
26272
|
const prov = cfg[provider] ?? {};
|
|
26146
26273
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26147
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26148
|
-
const tokenPath =
|
|
26274
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26275
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26149
26276
|
let tokenOk = false;
|
|
26150
26277
|
if (fs7.existsSync(tokenPath)) {
|
|
26151
26278
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26154,7 +26281,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26154
26281
|
}
|
|
26155
26282
|
const out = {
|
|
26156
26283
|
ok: true,
|
|
26157
|
-
configPath:
|
|
26284
|
+
configPath: path10.resolve(cfgPath),
|
|
26158
26285
|
provider,
|
|
26159
26286
|
defaultTargetBranch: defaultTarget,
|
|
26160
26287
|
pr: cfg.pr ?? {},
|
|
@@ -26247,18 +26374,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26247
26374
|
|
|
26248
26375
|
// packages/workit-core/src/core/pr-create.ts
|
|
26249
26376
|
import fs8 from "node:fs";
|
|
26250
|
-
import
|
|
26377
|
+
import path11 from "node:path";
|
|
26251
26378
|
function parseGhIssue(value) {
|
|
26252
26379
|
const m = /issues\/(\d+)/.exec(value);
|
|
26253
26380
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26254
26381
|
}
|
|
26255
26382
|
function whichOnPath(tool) {
|
|
26256
26383
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26257
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26384
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26258
26385
|
if (!dir)
|
|
26259
26386
|
continue;
|
|
26260
26387
|
for (const name of names) {
|
|
26261
|
-
const candidate =
|
|
26388
|
+
const candidate = path11.join(dir, name);
|
|
26262
26389
|
try {
|
|
26263
26390
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26264
26391
|
return candidate;
|
|
@@ -26271,8 +26398,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26271
26398
|
|
|
26272
26399
|
// packages/workit-core/src/core/repo-context.ts
|
|
26273
26400
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26274
|
-
import { existsSync as
|
|
26275
|
-
import
|
|
26401
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26402
|
+
import path12 from "node:path";
|
|
26276
26403
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26277
26404
|
try {
|
|
26278
26405
|
gitRevisionParts(value);
|
|
@@ -26406,7 +26533,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26406
26533
|
]) {
|
|
26407
26534
|
const parts = rel.split("/");
|
|
26408
26535
|
const base = parts[parts.length - 1];
|
|
26409
|
-
const dir = parts.length > 1 ?
|
|
26536
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26410
26537
|
const actual = probeCaseInsensitive(dir, base);
|
|
26411
26538
|
if (actual)
|
|
26412
26539
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26437,8 +26564,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26437
26564
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26438
26565
|
};
|
|
26439
26566
|
var packageScripts = (cwd, keys) => {
|
|
26440
|
-
const pkgPath =
|
|
26441
|
-
if (!
|
|
26567
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26568
|
+
if (!existsSync6(pkgPath))
|
|
26442
26569
|
return [];
|
|
26443
26570
|
try {
|
|
26444
26571
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26463,9 +26590,9 @@ var documentationFiles = (cwd) => {
|
|
|
26463
26590
|
if (entry.isDirectory()) {
|
|
26464
26591
|
if (excluded.has(entry.name))
|
|
26465
26592
|
continue;
|
|
26466
|
-
walk(
|
|
26593
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26467
26594
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26468
|
-
matches.push("./" +
|
|
26595
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26469
26596
|
}
|
|
26470
26597
|
}
|
|
26471
26598
|
};
|
|
@@ -26473,7 +26600,7 @@ var documentationFiles = (cwd) => {
|
|
|
26473
26600
|
return matches.sort().slice(0, 200);
|
|
26474
26601
|
};
|
|
26475
26602
|
var readTrimmed = (file, maxLines) => {
|
|
26476
|
-
if (!
|
|
26603
|
+
if (!existsSync6(file))
|
|
26477
26604
|
return "";
|
|
26478
26605
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26479
26606
|
`);
|
|
@@ -26481,7 +26608,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26481
26608
|
`);
|
|
26482
26609
|
};
|
|
26483
26610
|
function prReadyContext(root, range) {
|
|
26484
|
-
const cwd =
|
|
26611
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26485
26612
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26486
26613
|
return invalidRange(cwd);
|
|
26487
26614
|
let stdout = "";
|
|
@@ -26539,7 +26666,7 @@ ${status.stderr}` : "");
|
|
|
26539
26666
|
stdout += `template_path: ${template}
|
|
26540
26667
|
|
|
26541
26668
|
`;
|
|
26542
|
-
stdout += readTrimmed(
|
|
26669
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26543
26670
|
} else {
|
|
26544
26671
|
stdout += `template_path: none
|
|
26545
26672
|
|
|
@@ -26549,7 +26676,7 @@ ${status.stderr}` : "");
|
|
|
26549
26676
|
stdout += `
|
|
26550
26677
|
`;
|
|
26551
26678
|
stdout += printSection("Recent Validation Signals");
|
|
26552
|
-
if (
|
|
26679
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26553
26680
|
stdout += `package.json detected. Common scripts:
|
|
26554
26681
|
`;
|
|
26555
26682
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26558,7 +26685,7 @@ ${status.stderr}` : "");
|
|
|
26558
26685
|
`) + `
|
|
26559
26686
|
`;
|
|
26560
26687
|
}
|
|
26561
|
-
if (
|
|
26688
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26562
26689
|
stdout += `Rust project detected.
|
|
26563
26690
|
`;
|
|
26564
26691
|
}
|
|
@@ -26591,7 +26718,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26591
26718
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26592
26719
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26593
26720
|
function changelogContext(root, range) {
|
|
26594
|
-
const cwd =
|
|
26721
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26595
26722
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26596
26723
|
return invalidRange(cwd);
|
|
26597
26724
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26609,8 +26736,8 @@ function changelogContext(root, range) {
|
|
|
26609
26736
|
stdout += CHANGELOG_RULES + `
|
|
26610
26737
|
`;
|
|
26611
26738
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26612
|
-
const changelogPath =
|
|
26613
|
-
if (
|
|
26739
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26740
|
+
if (existsSync6(changelogPath)) {
|
|
26614
26741
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26615
26742
|
`;
|
|
26616
26743
|
} else {
|
|
@@ -26629,7 +26756,7 @@ function changelogContext(root, range) {
|
|
|
26629
26756
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26630
26757
|
}
|
|
26631
26758
|
function docsRefreshContext(root, range) {
|
|
26632
|
-
const cwd =
|
|
26759
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26633
26760
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26634
26761
|
return invalidRange(cwd);
|
|
26635
26762
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26651,8 +26778,8 @@ function docsRefreshContext(root, range) {
|
|
|
26651
26778
|
`) + `
|
|
26652
26779
|
`;
|
|
26653
26780
|
stdout += printSection("README Preview");
|
|
26654
|
-
const readme =
|
|
26655
|
-
if (
|
|
26781
|
+
const readme = path12.join(cwd, "README.md");
|
|
26782
|
+
if (existsSync6(readme)) {
|
|
26656
26783
|
stdout += readTrimmed(readme, 220) + `
|
|
26657
26784
|
`;
|
|
26658
26785
|
} else {
|
|
@@ -26660,8 +26787,8 @@ function docsRefreshContext(root, range) {
|
|
|
26660
26787
|
`;
|
|
26661
26788
|
}
|
|
26662
26789
|
stdout += printSection("Package Scripts");
|
|
26663
|
-
const pkgPath =
|
|
26664
|
-
if (
|
|
26790
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26791
|
+
if (existsSync6(pkgPath)) {
|
|
26665
26792
|
try {
|
|
26666
26793
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26667
26794
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26674,7 +26801,7 @@ function docsRefreshContext(root, range) {
|
|
|
26674
26801
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26675
26802
|
}
|
|
26676
26803
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26677
|
-
const cwd =
|
|
26804
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26678
26805
|
if (!rangeOrTag) {
|
|
26679
26806
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26680
26807
|
`, exitCode: 1, cwd };
|
|
@@ -26712,7 +26839,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26712
26839
|
`;
|
|
26713
26840
|
stdout += printSection("Existing Release Files");
|
|
26714
26841
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26715
|
-
if (
|
|
26842
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26716
26843
|
stdout += rel + `
|
|
26717
26844
|
`;
|
|
26718
26845
|
}
|
|
@@ -26903,25 +27030,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26903
27030
|
import fs10 from "node:fs";
|
|
26904
27031
|
import path13 from "node:path";
|
|
26905
27032
|
|
|
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
27033
|
// packages/workit-core/src/core/templates.ts
|
|
26926
27034
|
var repoRoot2 = assetRoot();
|
|
26927
27035
|
|
|
@@ -27425,6 +27533,32 @@ var toolInputSchema = (family) => {
|
|
|
27425
27533
|
const schema = boundedOperationJsonSchema(family);
|
|
27426
27534
|
return { type: "object", ...schema };
|
|
27427
27535
|
};
|
|
27536
|
+
var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
|
|
27537
|
+
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
27538
|
+
const action = branch.properties?.action?.const;
|
|
27539
|
+
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
27540
|
+
});
|
|
27541
|
+
var advertisedToolSchemas = (attested) => {
|
|
27542
|
+
if (attested)
|
|
27543
|
+
return OPERATION_FAMILIES.map((family) => ({
|
|
27544
|
+
name: `workit_${family}`,
|
|
27545
|
+
description: operationDescription(family),
|
|
27546
|
+
inputSchema: toolInputSchema(family)
|
|
27547
|
+
}));
|
|
27548
|
+
return OPERATION_FAMILIES.flatMap((family) => {
|
|
27549
|
+
const branches = readOnlyBranches(family);
|
|
27550
|
+
if (!branches.length)
|
|
27551
|
+
return [];
|
|
27552
|
+
const schema = boundedOperationJsonSchema(family);
|
|
27553
|
+
return [
|
|
27554
|
+
{
|
|
27555
|
+
name: `workit_${family}`,
|
|
27556
|
+
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27557
|
+
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
27558
|
+
}
|
|
27559
|
+
];
|
|
27560
|
+
});
|
|
27561
|
+
};
|
|
27428
27562
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27429
27563
|
if (result.ok)
|
|
27430
27564
|
return result;
|
|
@@ -27479,13 +27613,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27479
27613
|
].filter((file) => existsSync7(file)));
|
|
27480
27614
|
let staleWarned = false;
|
|
27481
27615
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27482
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27483
|
-
|
|
27484
|
-
|
|
27485
|
-
|
|
27486
|
-
|
|
27487
|
-
}
|
|
27488
|
-
|
|
27616
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27617
|
+
let attested = false;
|
|
27618
|
+
try {
|
|
27619
|
+
const context = await contextProvider.current();
|
|
27620
|
+
attested = context.callerAttested === true;
|
|
27621
|
+
} catch {
|
|
27622
|
+
attested = false;
|
|
27623
|
+
}
|
|
27624
|
+
return { tools: advertisedToolSchemas(attested) };
|
|
27625
|
+
});
|
|
27489
27626
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27490
27627
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27491
27628
|
uri: `workit://context/${kind}`,
|
|
@@ -27600,7 +27737,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27600
27737
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27601
27738
|
if (!parsed.ok)
|
|
27602
27739
|
return resultForClient(parsed, workspaceRoot);
|
|
27603
|
-
if (context.callerAttested
|
|
27740
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27604
27741
|
return resultForClient({
|
|
27605
27742
|
ok: false,
|
|
27606
27743
|
schemaVersion: 1,
|
|
@@ -27660,6 +27797,13 @@ var codexCapabilities = (host, availability = {}) => {
|
|
|
27660
27797
|
reason: "file writes are host-policy; PreToolUse allows write tools",
|
|
27661
27798
|
refs: [ref(host, "PreToolUse")]
|
|
27662
27799
|
},
|
|
27800
|
+
{
|
|
27801
|
+
name: "fresh-context-review",
|
|
27802
|
+
surface: "SubagentStart",
|
|
27803
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
27804
|
+
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",
|
|
27805
|
+
refs: [ref(host, "SubagentStart")]
|
|
27806
|
+
},
|
|
27663
27807
|
{
|
|
27664
27808
|
name: "native_subagents",
|
|
27665
27809
|
surface: "SubagentStart/SubagentStop",
|