@brainervirus/workit-codex 8.9.0 → 8.10.0
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 +240 -105
- package/dist/{workit-hook-aw8j630z.js → workit-hook-xhqhe7h0.js} +545 -369
- package/dist/workit-hook.js +1 -1
- package/package.json +3 -3
- package/skills/workit-fanout/references/brief.md +1 -1
- package/skills/workit-fanout/references/orchestration.md +16 -6
- package/skills/workit-implement/SKILL.md +3 -2
- package/skills/workit-review/SKILL.md +4 -2
|
@@ -24,9 +24,9 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
24
24
|
};
|
|
25
25
|
});
|
|
26
26
|
// packages/workit-core/src/hooks/context.ts
|
|
27
|
-
import { createHash as
|
|
28
|
-
import
|
|
29
|
-
import
|
|
27
|
+
import { createHash as createHash11 } from "node:crypto";
|
|
28
|
+
import fs14 from "node:fs";
|
|
29
|
+
import path17 from "node:path";
|
|
30
30
|
|
|
31
31
|
// packages/workit-core/src/core/methods.ts
|
|
32
32
|
var METHODS = {
|
|
@@ -101,7 +101,8 @@ Autonomy contract:
|
|
|
101
101
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
102
102
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
103
103
|
self-reviewed, never verified. High risk, \`verification: independent\` and
|
|
104
|
-
merge need a verifier with its own session (
|
|
104
|
+
merge need a verifier with its own host-derived session (a subagent's
|
|
105
|
+
\`<lead>:<child>\`, set by the host); never hand-mint one such as \`<lead>-v1\`.
|
|
105
106
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
106
107
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
107
108
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -8787,10 +8788,10 @@ function requirementId(input) {
|
|
|
8787
8788
|
}
|
|
8788
8789
|
|
|
8789
8790
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
8790
|
-
import { createHash as
|
|
8791
|
-
import * as
|
|
8791
|
+
import { createHash as createHash10 } from "node:crypto";
|
|
8792
|
+
import * as fs13 from "node:fs";
|
|
8792
8793
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
8793
|
-
import
|
|
8794
|
+
import path15 from "node:path";
|
|
8794
8795
|
|
|
8795
8796
|
// packages/workit-core/src/check-config.ts
|
|
8796
8797
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -9179,9 +9180,9 @@ function pushRemoteName(cwd, branch) {
|
|
|
9179
9180
|
}
|
|
9180
9181
|
|
|
9181
9182
|
// packages/workit-core/src/core/task-store.ts
|
|
9182
|
-
import { createHash as
|
|
9183
|
-
import * as
|
|
9184
|
-
import
|
|
9183
|
+
import { createHash as createHash6, randomUUID as randomUUID3 } from "node:crypto";
|
|
9184
|
+
import * as fs10 from "node:fs";
|
|
9185
|
+
import path9 from "node:path";
|
|
9185
9186
|
|
|
9186
9187
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
9187
9188
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -9785,6 +9786,9 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9785
9786
|
}
|
|
9786
9787
|
}
|
|
9787
9788
|
// packages/workit-core/src/host-session.ts
|
|
9789
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
9790
|
+
import fs7 from "node:fs";
|
|
9791
|
+
import path6 from "node:path";
|
|
9788
9792
|
var HOST_SESSION_VARS = [
|
|
9789
9793
|
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
9790
9794
|
["PI_SESSION_ID", () => "pi"],
|
|
@@ -9812,16 +9816,44 @@ function hostSessionFromEnv(env) {
|
|
|
9812
9816
|
}
|
|
9813
9817
|
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9814
9818
|
}
|
|
9819
|
+
var ISSUED_PER_LEAD = 64;
|
|
9820
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/1";
|
|
9821
|
+
var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
|
|
9822
|
+
var issuedRoot = (cwd) => {
|
|
9823
|
+
const location = resolveStore(cwd);
|
|
9824
|
+
return location instanceof Error ? null : path6.join(location.dir, "hooks", "issued");
|
|
9825
|
+
};
|
|
9826
|
+
var issuedDir = (cwd, lead) => {
|
|
9827
|
+
const root = issuedRoot(cwd);
|
|
9828
|
+
return root === null ? null : path6.join(root, hash(lead));
|
|
9829
|
+
};
|
|
9830
|
+
function recordIssuedSession(cwd, lead, issued) {
|
|
9831
|
+
try {
|
|
9832
|
+
const dir = issuedDir(cwd, lead);
|
|
9833
|
+
if (!dir)
|
|
9834
|
+
return;
|
|
9835
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
9836
|
+
const own = hash(issued);
|
|
9837
|
+
fs7.writeFileSync(path6.join(dir, own), ISSUED_SESSIONS_MARKER);
|
|
9838
|
+
const entries = fs7.readdirSync(dir);
|
|
9839
|
+
if (entries.length <= ISSUED_PER_LEAD)
|
|
9840
|
+
return;
|
|
9841
|
+
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path6.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9842
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
9843
|
+
fs7.rmSync(path6.join(dir, name), { force: true });
|
|
9844
|
+
} catch {}
|
|
9845
|
+
}
|
|
9846
|
+
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9815
9847
|
|
|
9816
9848
|
// packages/workit-core/src/core/package-root.ts
|
|
9817
9849
|
import { existsSync } from "node:fs";
|
|
9818
|
-
import
|
|
9850
|
+
import path7 from "node:path";
|
|
9819
9851
|
import { fileURLToPath } from "node:url";
|
|
9820
9852
|
var packageRoot = () => {
|
|
9821
|
-
let dir =
|
|
9853
|
+
let dir = path7.dirname(fileURLToPath(import.meta.url));
|
|
9822
9854
|
while (true) {
|
|
9823
|
-
const parent =
|
|
9824
|
-
if (existsSync(
|
|
9855
|
+
const parent = path7.dirname(dir);
|
|
9856
|
+
if (existsSync(path7.join(dir, "package.json")) || parent === dir)
|
|
9825
9857
|
return dir;
|
|
9826
9858
|
dir = parent;
|
|
9827
9859
|
}
|
|
@@ -9829,7 +9861,7 @@ var packageRoot = () => {
|
|
|
9829
9861
|
|
|
9830
9862
|
// packages/workit-core/src/core/store-lock.ts
|
|
9831
9863
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
9832
|
-
import
|
|
9864
|
+
import fs8 from "node:fs";
|
|
9833
9865
|
import { hostname } from "node:os";
|
|
9834
9866
|
var metadataLockSchema = object({
|
|
9835
9867
|
pid: number2().int().nonnegative().safe(),
|
|
@@ -9871,7 +9903,7 @@ var parseProcStatStart = (stat) => {
|
|
|
9871
9903
|
var processStartOf = (pid) => {
|
|
9872
9904
|
if (process.platform === "linux") {
|
|
9873
9905
|
try {
|
|
9874
|
-
return parseProcStatStart(
|
|
9906
|
+
return parseProcStatStart(fs8.readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
9875
9907
|
} catch {
|
|
9876
9908
|
return null;
|
|
9877
9909
|
}
|
|
@@ -9892,7 +9924,7 @@ var processStartOf = (pid) => {
|
|
|
9892
9924
|
};
|
|
9893
9925
|
var readTrimmed = (file) => {
|
|
9894
9926
|
try {
|
|
9895
|
-
return
|
|
9927
|
+
return fs8.readFileSync(file, "utf8").trim() || null;
|
|
9896
9928
|
} catch {
|
|
9897
9929
|
return null;
|
|
9898
9930
|
}
|
|
@@ -9903,7 +9935,7 @@ var localLockHost = () => {
|
|
|
9903
9935
|
return cachedLockHost;
|
|
9904
9936
|
let pidns = null;
|
|
9905
9937
|
try {
|
|
9906
|
-
pidns = /\[(\d+)\]/.exec(
|
|
9938
|
+
pidns = /\[(\d+)\]/.exec(fs8.readlinkSync("/proc/self/ns/pid"))?.[1] ?? null;
|
|
9907
9939
|
} catch {}
|
|
9908
9940
|
const boot = readTrimmed("/proc/sys/kernel/random/boot_id");
|
|
9909
9941
|
cachedLockHost = pidns || boot ? `${hostname()}#${pidns ?? "?"}:${boot ?? "?"}` : hostname();
|
|
@@ -9949,7 +9981,7 @@ var classifyLockOwner = (payload, ageMs, localHost = localLockHost(), options =
|
|
|
9949
9981
|
};
|
|
9950
9982
|
var ageOf = (file, nowMs) => {
|
|
9951
9983
|
try {
|
|
9952
|
-
return nowMs -
|
|
9984
|
+
return nowMs - fs8.lstatSync(file).mtimeMs;
|
|
9953
9985
|
} catch {
|
|
9954
9986
|
return null;
|
|
9955
9987
|
}
|
|
@@ -9960,7 +9992,7 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
9960
9992
|
if (age === null || age <= RECLAIM_GUARD_TTL_MS)
|
|
9961
9993
|
return false;
|
|
9962
9994
|
try {
|
|
9963
|
-
|
|
9995
|
+
fs8.rmdirSync(guard);
|
|
9964
9996
|
return true;
|
|
9965
9997
|
} catch {
|
|
9966
9998
|
return false;
|
|
@@ -9969,8 +10001,8 @@ var clearAbandonedReclaimGuard = (lockPath, nowMs = Date.now()) => {
|
|
|
9969
10001
|
|
|
9970
10002
|
// packages/workit-core/src/store/event-log.ts
|
|
9971
10003
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
9972
|
-
import
|
|
9973
|
-
import
|
|
10004
|
+
import fs9 from "node:fs";
|
|
10005
|
+
import path8 from "node:path";
|
|
9974
10006
|
var EVENT_VERSION = 1;
|
|
9975
10007
|
|
|
9976
10008
|
class LogDamage extends Error {
|
|
@@ -9999,7 +10031,7 @@ var parseEvent = (line, file, offset) => {
|
|
|
9999
10031
|
function readLog(file, from = 0, expectSeq = null) {
|
|
10000
10032
|
let fd;
|
|
10001
10033
|
try {
|
|
10002
|
-
fd =
|
|
10034
|
+
fd = fs9.openSync(file, "r");
|
|
10003
10035
|
} catch (error) {
|
|
10004
10036
|
if (error.code === "ENOENT")
|
|
10005
10037
|
return null;
|
|
@@ -10008,13 +10040,13 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10008
10040
|
let bytes;
|
|
10009
10041
|
let size;
|
|
10010
10042
|
try {
|
|
10011
|
-
size =
|
|
10043
|
+
size = fs9.fstatSync(fd).size;
|
|
10012
10044
|
if (from > size)
|
|
10013
10045
|
throw new LogDamage(`${file}: shorter than its snapshot`);
|
|
10014
10046
|
bytes = Buffer.alloc(size - from);
|
|
10015
10047
|
let read = 0;
|
|
10016
10048
|
while (read < bytes.length) {
|
|
10017
|
-
const got =
|
|
10049
|
+
const got = fs9.readSync(fd, bytes, read, bytes.length - read, from + read);
|
|
10018
10050
|
if (got === 0)
|
|
10019
10051
|
break;
|
|
10020
10052
|
read += got;
|
|
@@ -10022,7 +10054,7 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10022
10054
|
bytes = bytes.subarray(0, read);
|
|
10023
10055
|
size = from + read;
|
|
10024
10056
|
} finally {
|
|
10025
|
-
|
|
10057
|
+
fs9.closeSync(fd);
|
|
10026
10058
|
}
|
|
10027
10059
|
const events = [];
|
|
10028
10060
|
let start = 0;
|
|
@@ -10047,11 +10079,11 @@ function readLog(file, from = 0, expectSeq = null) {
|
|
|
10047
10079
|
}
|
|
10048
10080
|
var fsyncDirectory = (dir) => {
|
|
10049
10081
|
try {
|
|
10050
|
-
const fd =
|
|
10082
|
+
const fd = fs9.openSync(dir, "r");
|
|
10051
10083
|
try {
|
|
10052
|
-
|
|
10084
|
+
fs9.fsyncSync(fd);
|
|
10053
10085
|
} finally {
|
|
10054
|
-
|
|
10086
|
+
fs9.closeSync(fd);
|
|
10055
10087
|
}
|
|
10056
10088
|
} catch (error) {
|
|
10057
10089
|
if (process.platform !== "win32")
|
|
@@ -10061,46 +10093,46 @@ var fsyncDirectory = (dir) => {
|
|
|
10061
10093
|
function appendEvent(file, event, validEnd) {
|
|
10062
10094
|
const line = Buffer.from(`${JSON.stringify(event)}
|
|
10063
10095
|
`, "utf8");
|
|
10064
|
-
const created = !
|
|
10065
|
-
if (!created &&
|
|
10066
|
-
const cut =
|
|
10096
|
+
const created = !fs9.existsSync(file);
|
|
10097
|
+
if (!created && fs9.statSync(file).size !== validEnd) {
|
|
10098
|
+
const cut = fs9.openSync(file, "r+");
|
|
10067
10099
|
try {
|
|
10068
|
-
|
|
10069
|
-
|
|
10100
|
+
fs9.ftruncateSync(cut, validEnd);
|
|
10101
|
+
fs9.fsyncSync(cut);
|
|
10070
10102
|
} finally {
|
|
10071
|
-
|
|
10103
|
+
fs9.closeSync(cut);
|
|
10072
10104
|
}
|
|
10073
10105
|
}
|
|
10074
|
-
const fd =
|
|
10106
|
+
const fd = fs9.openSync(file, "a", 384);
|
|
10075
10107
|
try {
|
|
10076
10108
|
let written = 0;
|
|
10077
10109
|
while (written < line.length)
|
|
10078
|
-
written +=
|
|
10079
|
-
|
|
10110
|
+
written += fs9.writeSync(fd, line, written, line.length - written);
|
|
10111
|
+
fs9.fsyncSync(fd);
|
|
10080
10112
|
} finally {
|
|
10081
|
-
|
|
10113
|
+
fs9.closeSync(fd);
|
|
10082
10114
|
}
|
|
10083
10115
|
if (created)
|
|
10084
|
-
fsyncDirectory(
|
|
10116
|
+
fsyncDirectory(path8.dirname(file));
|
|
10085
10117
|
return validEnd;
|
|
10086
10118
|
}
|
|
10087
10119
|
function replaceFile(file, bytes, durable) {
|
|
10088
10120
|
const temporary = `${file}.${process.pid}.${randomUUID2()}.tmp`;
|
|
10089
10121
|
try {
|
|
10090
|
-
const fd =
|
|
10122
|
+
const fd = fs9.openSync(temporary, "wx", 384);
|
|
10091
10123
|
try {
|
|
10092
|
-
|
|
10124
|
+
fs9.writeSync(fd, bytes);
|
|
10093
10125
|
if (durable)
|
|
10094
|
-
|
|
10126
|
+
fs9.fsyncSync(fd);
|
|
10095
10127
|
} finally {
|
|
10096
|
-
|
|
10128
|
+
fs9.closeSync(fd);
|
|
10097
10129
|
}
|
|
10098
10130
|
renameWithRetry(temporary, file);
|
|
10099
10131
|
if (durable)
|
|
10100
|
-
fsyncDirectory(
|
|
10132
|
+
fsyncDirectory(path8.dirname(file));
|
|
10101
10133
|
} catch (error) {
|
|
10102
10134
|
try {
|
|
10103
|
-
|
|
10135
|
+
fs9.unlinkSync(temporary);
|
|
10104
10136
|
} catch {}
|
|
10105
10137
|
throw error;
|
|
10106
10138
|
}
|
|
@@ -10125,7 +10157,7 @@ var retryTransient = (run) => {
|
|
|
10125
10157
|
}
|
|
10126
10158
|
}
|
|
10127
10159
|
};
|
|
10128
|
-
var renameWithRetry = (from, to) => retryTransient(() =>
|
|
10160
|
+
var renameWithRetry = (from, to) => retryTransient(() => fs9.renameSync(from, to));
|
|
10129
10161
|
|
|
10130
10162
|
// packages/workit-core/src/store/patch.ts
|
|
10131
10163
|
var isPlainObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -10238,7 +10270,7 @@ var isPatch = (value) => Array.isArray(value) && value.every((op) => {
|
|
|
10238
10270
|
});
|
|
10239
10271
|
|
|
10240
10272
|
// packages/workit-core/src/store/reduce.ts
|
|
10241
|
-
import { createHash as
|
|
10273
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
10242
10274
|
var OPENING_TYPES = new Set([
|
|
10243
10275
|
"task.opened",
|
|
10244
10276
|
"task.imported",
|
|
@@ -10267,7 +10299,7 @@ var isObject3 = (value) => typeof value === "object" && value !== null && !Array
|
|
|
10267
10299
|
var isKey = (value) => isObject3(value) && typeof value.key === "string" && (value.kind === "branch" || value.kind === "detached" || value.kind === "dir") && (value.branch === null || typeof value.branch === "string");
|
|
10268
10300
|
var BLOB_REF = "$blob";
|
|
10269
10301
|
var blobRef = (value) => isObject3(value) && Object.keys(value).length === 1 && typeof value[BLOB_REF] === "string" && /^[0-9a-f]{64}$/.test(value[BLOB_REF]) ? value[BLOB_REF] : null;
|
|
10270
|
-
var blobDigest = (value) =>
|
|
10302
|
+
var blobDigest = (value) => createHash5("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10271
10303
|
var toBlob = (value, blobs) => {
|
|
10272
10304
|
if (!isObject3(value))
|
|
10273
10305
|
return value;
|
|
@@ -10422,7 +10454,7 @@ var indexEntry = (task, key, legacy, file) => {
|
|
|
10422
10454
|
};
|
|
10423
10455
|
var fileSignature = (file) => {
|
|
10424
10456
|
try {
|
|
10425
|
-
const stat =
|
|
10457
|
+
const stat = fs10.statSync(file, { bigint: true });
|
|
10426
10458
|
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
|
|
10427
10459
|
} catch {
|
|
10428
10460
|
return null;
|
|
@@ -10440,7 +10472,7 @@ var cachedRuntimeVersion = null;
|
|
|
10440
10472
|
var runtimeVersion = () => {
|
|
10441
10473
|
if (cachedRuntimeVersion === null) {
|
|
10442
10474
|
try {
|
|
10443
|
-
const pkg = JSON.parse(
|
|
10475
|
+
const pkg = JSON.parse(fs10.readFileSync(path9.join(packageRoot(), "package.json"), "utf8"));
|
|
10444
10476
|
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
10445
10477
|
} catch {
|
|
10446
10478
|
cachedRuntimeVersion = "0.0.0";
|
|
@@ -10464,20 +10496,52 @@ var isNewerVersion = (candidate, current) => {
|
|
|
10464
10496
|
return false;
|
|
10465
10497
|
};
|
|
10466
10498
|
var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
10499
|
+
var readText = (file) => {
|
|
10500
|
+
try {
|
|
10501
|
+
return fs10.readFileSync(file, "utf8");
|
|
10502
|
+
} catch {
|
|
10503
|
+
return "";
|
|
10504
|
+
}
|
|
10505
|
+
};
|
|
10506
|
+
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
10507
|
+
var branchStart = (location, branch, aliases) => {
|
|
10508
|
+
const first = readText(path9.join(path9.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
10509
|
+
`, 1)[0];
|
|
10510
|
+
const created = REFLOG_LINE.exec(first);
|
|
10511
|
+
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
10512
|
+
if (!created || !start)
|
|
10513
|
+
return null;
|
|
10514
|
+
if (start !== "HEAD")
|
|
10515
|
+
return start;
|
|
10516
|
+
if (!location.git)
|
|
10517
|
+
return null;
|
|
10518
|
+
for (const line of readText(path9.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
10519
|
+
`)) {
|
|
10520
|
+
const entry = REFLOG_LINE.exec(line);
|
|
10521
|
+
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
10522
|
+
const seconds = entry ? Number(entry[2]) - Number(created[2]) : -1;
|
|
10523
|
+
if (moved && entry[1] === created[1] && seconds >= 0 && seconds <= 1 && aliases.includes(moved[2]))
|
|
10524
|
+
return moved[1];
|
|
10525
|
+
}
|
|
10526
|
+
return null;
|
|
10527
|
+
};
|
|
10528
|
+
var refExists = (common, ref) => fs10.existsSync(path9.join(common, ref)) || readText(path9.join(common, "packed-refs")).includes(` ${ref}
|
|
10529
|
+
`);
|
|
10530
|
+
var origins = new Map;
|
|
10467
10531
|
var sameDirectoryIdentity = (left, right) => {
|
|
10468
|
-
if (!
|
|
10532
|
+
if (!path9.isAbsolute(left) || !path9.isAbsolute(right))
|
|
10469
10533
|
return false;
|
|
10470
|
-
const normalizedLeft =
|
|
10471
|
-
const normalizedRight =
|
|
10534
|
+
const normalizedLeft = path9.resolve(left);
|
|
10535
|
+
const normalizedRight = path9.resolve(right);
|
|
10472
10536
|
try {
|
|
10473
|
-
const a =
|
|
10474
|
-
const b =
|
|
10537
|
+
const a = fs10.statSync(normalizedLeft, { bigint: true });
|
|
10538
|
+
const b = fs10.statSync(normalizedRight, { bigint: true });
|
|
10475
10539
|
if (!a.isDirectory() || !b.isDirectory())
|
|
10476
10540
|
return false;
|
|
10477
10541
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
10478
10542
|
return true;
|
|
10479
|
-
const canonicalLeft =
|
|
10480
|
-
const canonicalRight =
|
|
10543
|
+
const canonicalLeft = fs10.realpathSync(normalizedLeft);
|
|
10544
|
+
const canonicalRight = fs10.realpathSync(normalizedRight);
|
|
10481
10545
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
10482
10546
|
return true;
|
|
10483
10547
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -10514,8 +10578,8 @@ class TaskStore {
|
|
|
10514
10578
|
migrationChecked = false;
|
|
10515
10579
|
compactDue = new Set;
|
|
10516
10580
|
constructor(root, options = {}) {
|
|
10517
|
-
const resolved =
|
|
10518
|
-
this.origin =
|
|
10581
|
+
const resolved = path9.resolve(root);
|
|
10582
|
+
this.origin = fs10.existsSync(resolved) ? fs10.realpathSync(resolved) : resolved;
|
|
10519
10583
|
this.located = resolveStore(this.origin);
|
|
10520
10584
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
10521
10585
|
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
@@ -10523,7 +10587,7 @@ class TaskStore {
|
|
|
10523
10587
|
this.migrateOnRead = options.migrateOnRead;
|
|
10524
10588
|
}
|
|
10525
10589
|
location() {
|
|
10526
|
-
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared &&
|
|
10590
|
+
if (this.located !== undefined && !(this.located instanceof Error) && !this.located.shared && fs10.existsSync(path9.join(this.root, ".git"))) {
|
|
10527
10591
|
this.located = undefined;
|
|
10528
10592
|
this.migrationChecked = false;
|
|
10529
10593
|
}
|
|
@@ -10587,7 +10651,7 @@ class TaskStore {
|
|
|
10587
10651
|
const tasksDir = this.paths().tasks;
|
|
10588
10652
|
let names;
|
|
10589
10653
|
try {
|
|
10590
|
-
names =
|
|
10654
|
+
names = fs10.existsSync(tasksDir) ? fs10.readdirSync(tasksDir).filter((name) => validId(name)) : [];
|
|
10591
10655
|
} catch (error) {
|
|
10592
10656
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
10593
10657
|
path: tasksDir
|
|
@@ -10628,7 +10692,7 @@ class TaskStore {
|
|
|
10628
10692
|
const file = this.paths().workspace;
|
|
10629
10693
|
let bytes;
|
|
10630
10694
|
try {
|
|
10631
|
-
bytes = retryTransient(() =>
|
|
10695
|
+
bytes = retryTransient(() => fs10.readFileSync(file, "utf8"));
|
|
10632
10696
|
} catch (error) {
|
|
10633
10697
|
if (error.code === "ENOENT")
|
|
10634
10698
|
return success(null, null, null);
|
|
@@ -10679,7 +10743,7 @@ class TaskStore {
|
|
|
10679
10743
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
10680
10744
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
10681
10745
|
}
|
|
10682
|
-
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" :
|
|
10746
|
+
const label = key.kind === "branch" ? `branch ${key.branch}` : key.kind === "detached" ? "a detached HEAD" : path9.basename(this.root) || this.root;
|
|
10683
10747
|
const intent = {
|
|
10684
10748
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
10685
10749
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -10716,6 +10780,66 @@ class TaskStore {
|
|
|
10716
10780
|
return bound;
|
|
10717
10781
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
10718
10782
|
}
|
|
10783
|
+
taskForWorker(workerId) {
|
|
10784
|
+
if (typeof workerId !== "string" || !validId(workerId))
|
|
10785
|
+
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
10786
|
+
const listed = this.listTaskIndex();
|
|
10787
|
+
if (!listed.ok)
|
|
10788
|
+
return listed;
|
|
10789
|
+
for (const entry of listed.data) {
|
|
10790
|
+
if (entry.status === "closed")
|
|
10791
|
+
continue;
|
|
10792
|
+
const loaded = this.load(entry.id);
|
|
10793
|
+
if (!loaded.ok)
|
|
10794
|
+
return loaded;
|
|
10795
|
+
if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
|
|
10796
|
+
return success(null, null, entry.id);
|
|
10797
|
+
}
|
|
10798
|
+
return success(null, null, null);
|
|
10799
|
+
}
|
|
10800
|
+
originTaskEntry() {
|
|
10801
|
+
const key = this.currentKey();
|
|
10802
|
+
const location = this.location();
|
|
10803
|
+
const branch = key.ok ? key.data.branch : null;
|
|
10804
|
+
if (!branch || !location.ok)
|
|
10805
|
+
return success(null, null, null);
|
|
10806
|
+
const common = path9.dirname(location.data.dir);
|
|
10807
|
+
const heads = path9.join(common, "logs", "refs", "heads");
|
|
10808
|
+
const headLog = location.data.git ? path9.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
10809
|
+
const signatures = [
|
|
10810
|
+
path9.join(heads, branch),
|
|
10811
|
+
headLog,
|
|
10812
|
+
heads,
|
|
10813
|
+
path9.join(common, "packed-refs")
|
|
10814
|
+
].map((file) => fileSignature(file) ?? "-");
|
|
10815
|
+
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
10816
|
+
let resolved = origins.get(slot);
|
|
10817
|
+
if (resolved?.signature !== signatures.join("|")) {
|
|
10818
|
+
const renames = branchRenames(location.data);
|
|
10819
|
+
const aliases = [branch];
|
|
10820
|
+
for (const rename of renames.toReversed())
|
|
10821
|
+
if (rename.to === aliases.at(-1))
|
|
10822
|
+
aliases.push(rename.from);
|
|
10823
|
+
const start = branchStart(location.data, branch, aliases);
|
|
10824
|
+
const slash = start?.indexOf("/") ?? -1;
|
|
10825
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path9.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
10826
|
+
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
10827
|
+
resolved = {
|
|
10828
|
+
signature: signatures.join("|"),
|
|
10829
|
+
name: name && !aliases.includes(name) ? name : null,
|
|
10830
|
+
renames
|
|
10831
|
+
};
|
|
10832
|
+
origins.set(slot, resolved);
|
|
10833
|
+
}
|
|
10834
|
+
const entry = resolved.name ? this.openTasksForKey(resolved.name, resolved.renames) : null;
|
|
10835
|
+
if (entry && !entry.ok)
|
|
10836
|
+
return entry;
|
|
10837
|
+
return success(null, null, entry?.data[0] ? { origin: `branch ${resolved.name}`, entry: entry.data[0] } : null);
|
|
10838
|
+
}
|
|
10839
|
+
readStoreTask(taskId) {
|
|
10840
|
+
const loaded = this.load(taskId);
|
|
10841
|
+
return loaded.ok ? success(null, null, loaded.data?.record ?? null) : loaded;
|
|
10842
|
+
}
|
|
10719
10843
|
create(input, provenance, expectedWorkspaceRevision) {
|
|
10720
10844
|
const value = "intent" in input ? input : {
|
|
10721
10845
|
intent: input,
|
|
@@ -10936,13 +11060,13 @@ class TaskStore {
|
|
|
10936
11060
|
if (!ready.ok)
|
|
10937
11061
|
return ready;
|
|
10938
11062
|
const dirs = this.paths();
|
|
10939
|
-
const recovery =
|
|
10940
|
-
if (
|
|
11063
|
+
const recovery = path9.join(this.root, ".workit", "recovery");
|
|
11064
|
+
if (fs10.existsSync(recovery)) {
|
|
10941
11065
|
let files = 0;
|
|
10942
11066
|
let bytes = 0;
|
|
10943
11067
|
try {
|
|
10944
|
-
for (const name of
|
|
10945
|
-
const stat =
|
|
11068
|
+
for (const name of fs10.readdirSync(recovery)) {
|
|
11069
|
+
const stat = fs10.lstatSync(path9.join(recovery, name));
|
|
10946
11070
|
if (!stat.isFile())
|
|
10947
11071
|
continue;
|
|
10948
11072
|
files += 1;
|
|
@@ -10951,17 +11075,17 @@ class TaskStore {
|
|
|
10951
11075
|
} catch {}
|
|
10952
11076
|
const remove = options.pruneRecovery === true && !dryRun;
|
|
10953
11077
|
if (remove)
|
|
10954
|
-
|
|
11078
|
+
fs10.rmSync(recovery, { recursive: true, force: true });
|
|
10955
11079
|
report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
|
|
10956
11080
|
}
|
|
10957
|
-
if (!
|
|
11081
|
+
if (!fs10.existsSync(dirs.tasks))
|
|
10958
11082
|
return success(null, null, report);
|
|
10959
|
-
const names =
|
|
11083
|
+
const names = fs10.readdirSync(dirs.tasks).filter((name) => validId(name));
|
|
10960
11084
|
for (const name of names) {
|
|
10961
11085
|
const file = this.eventsPath(name);
|
|
10962
11086
|
let size = 0;
|
|
10963
11087
|
try {
|
|
10964
|
-
size =
|
|
11088
|
+
size = fs10.statSync(file).size;
|
|
10965
11089
|
} catch {
|
|
10966
11090
|
continue;
|
|
10967
11091
|
}
|
|
@@ -10989,16 +11113,16 @@ class TaskStore {
|
|
|
10989
11113
|
const references = new Set;
|
|
10990
11114
|
for (const name of names)
|
|
10991
11115
|
try {
|
|
10992
|
-
for (const match of
|
|
11116
|
+
for (const match of fs10.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
|
|
10993
11117
|
references.add(match[1]);
|
|
10994
11118
|
} catch {}
|
|
10995
|
-
if (
|
|
10996
|
-
for (const name of
|
|
11119
|
+
if (fs10.existsSync(dirs.blobs))
|
|
11120
|
+
for (const name of fs10.readdirSync(dirs.blobs)) {
|
|
10997
11121
|
const digest = name.replace(/\.json$/, "");
|
|
10998
|
-
const file =
|
|
11122
|
+
const file = path9.join(dirs.blobs, name);
|
|
10999
11123
|
let stat;
|
|
11000
11124
|
try {
|
|
11001
|
-
stat =
|
|
11125
|
+
stat = fs10.lstatSync(file);
|
|
11002
11126
|
} catch {
|
|
11003
11127
|
continue;
|
|
11004
11128
|
}
|
|
@@ -11009,11 +11133,11 @@ class TaskStore {
|
|
|
11009
11133
|
report.blobs.removed += 1;
|
|
11010
11134
|
report.blobs.removedBytes += stat.size;
|
|
11011
11135
|
if (!dryRun)
|
|
11012
|
-
|
|
11136
|
+
fs10.rmSync(file, { force: true });
|
|
11013
11137
|
}
|
|
11014
11138
|
return success(null, null, null);
|
|
11015
11139
|
};
|
|
11016
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
11140
|
+
const swept = dryRun ? sweep() : this.withLock(path9.join(dirs.dir, "gc.lock"), sweep);
|
|
11017
11141
|
if (!swept.ok)
|
|
11018
11142
|
return swept;
|
|
11019
11143
|
const temporaryDirs = [
|
|
@@ -11021,17 +11145,17 @@ class TaskStore {
|
|
|
11021
11145
|
dirs.tasks,
|
|
11022
11146
|
dirs.blobs,
|
|
11023
11147
|
dirs.checkout,
|
|
11024
|
-
...names.map((name) =>
|
|
11148
|
+
...names.map((name) => path9.join(dirs.tasks, name))
|
|
11025
11149
|
];
|
|
11026
11150
|
for (const directory of temporaryDirs) {
|
|
11027
|
-
if (!
|
|
11151
|
+
if (!fs10.existsSync(directory))
|
|
11028
11152
|
continue;
|
|
11029
|
-
for (const name of
|
|
11153
|
+
for (const name of fs10.readdirSync(directory)) {
|
|
11030
11154
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
11031
11155
|
continue;
|
|
11032
|
-
const file =
|
|
11156
|
+
const file = path9.join(directory, name);
|
|
11033
11157
|
try {
|
|
11034
|
-
const stat =
|
|
11158
|
+
const stat = fs10.lstatSync(file);
|
|
11035
11159
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
11036
11160
|
continue;
|
|
11037
11161
|
} catch {
|
|
@@ -11039,37 +11163,37 @@ class TaskStore {
|
|
|
11039
11163
|
}
|
|
11040
11164
|
report.temporary.removed += 1;
|
|
11041
11165
|
if (!dryRun)
|
|
11042
|
-
|
|
11166
|
+
fs10.rmSync(file, { force: true });
|
|
11043
11167
|
}
|
|
11044
11168
|
}
|
|
11045
11169
|
return success(null, null, report);
|
|
11046
11170
|
}
|
|
11047
11171
|
paths() {
|
|
11048
11172
|
const location = this.location();
|
|
11049
|
-
const dir = location.ok ? location.data.dir :
|
|
11050
|
-
const checkout =
|
|
11173
|
+
const dir = location.ok ? location.data.dir : path9.join(this.root, ".workit");
|
|
11174
|
+
const checkout = path9.join(dir, "checkouts", checkoutSlug(this.root));
|
|
11051
11175
|
return {
|
|
11052
11176
|
dir,
|
|
11053
11177
|
shared: location.ok ? location.data.shared : false,
|
|
11054
|
-
marker:
|
|
11178
|
+
marker: path9.join(dir, "store.json"),
|
|
11055
11179
|
checkout,
|
|
11056
|
-
workspace:
|
|
11057
|
-
checkoutLock:
|
|
11058
|
-
tasks:
|
|
11059
|
-
index:
|
|
11060
|
-
blobs:
|
|
11061
|
-
legacy:
|
|
11062
|
-
v2:
|
|
11180
|
+
workspace: path9.join(checkout, "workspace.json"),
|
|
11181
|
+
checkoutLock: path9.join(checkout, "metadata.lock"),
|
|
11182
|
+
tasks: path9.join(dir, "tasks"),
|
|
11183
|
+
index: path9.join(dir, "task-index.json"),
|
|
11184
|
+
blobs: path9.join(dir, "blobs", "candidates"),
|
|
11185
|
+
legacy: path9.join(dir, "legacy", checkoutSlug(this.root), "v2"),
|
|
11186
|
+
v2: path9.join(this.root, ".workit")
|
|
11063
11187
|
};
|
|
11064
11188
|
}
|
|
11065
11189
|
eventsPath(taskId) {
|
|
11066
|
-
return
|
|
11190
|
+
return path9.join(this.paths().tasks, taskId, "events.jsonl");
|
|
11067
11191
|
}
|
|
11068
11192
|
snapshotPath(taskId) {
|
|
11069
|
-
return
|
|
11193
|
+
return path9.join(this.paths().tasks, taskId, "snapshot.json");
|
|
11070
11194
|
}
|
|
11071
11195
|
taskLockPath(taskId) {
|
|
11072
|
-
return
|
|
11196
|
+
return path9.join(this.paths().tasks, taskId, "lock");
|
|
11073
11197
|
}
|
|
11074
11198
|
ready(write = false) {
|
|
11075
11199
|
const location = this.location();
|
|
@@ -11079,7 +11203,7 @@ class TaskStore {
|
|
|
11079
11203
|
return success(null, null, null);
|
|
11080
11204
|
const dirs = this.paths();
|
|
11081
11205
|
try {
|
|
11082
|
-
const marker = JSON.parse(
|
|
11206
|
+
const marker = JSON.parse(fs10.readFileSync(dirs.marker, "utf8"));
|
|
11083
11207
|
if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
|
|
11084
11208
|
return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
|
|
11085
11209
|
} catch {}
|
|
@@ -11108,21 +11232,21 @@ class TaskStore {
|
|
|
11108
11232
|
return {
|
|
11109
11233
|
read: (digest) => {
|
|
11110
11234
|
try {
|
|
11111
|
-
return JSON.parse(
|
|
11235
|
+
return JSON.parse(fs10.readFileSync(path9.join(dir, `${digest}.json`), "utf8"));
|
|
11112
11236
|
} catch (error) {
|
|
11113
11237
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
11114
11238
|
}
|
|
11115
11239
|
},
|
|
11116
11240
|
write: (digest, value) => {
|
|
11117
|
-
const file =
|
|
11118
|
-
if (
|
|
11241
|
+
const file = path9.join(dir, `${digest}.json`);
|
|
11242
|
+
if (fs10.existsSync(file)) {
|
|
11119
11243
|
const stamp = new Date;
|
|
11120
11244
|
try {
|
|
11121
|
-
|
|
11245
|
+
fs10.utimesSync(file, stamp, stamp);
|
|
11122
11246
|
return;
|
|
11123
11247
|
} catch {}
|
|
11124
11248
|
}
|
|
11125
|
-
|
|
11249
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
11126
11250
|
replaceFile(file, JSON.stringify(value), true);
|
|
11127
11251
|
}
|
|
11128
11252
|
};
|
|
@@ -11204,7 +11328,7 @@ class TaskStore {
|
|
|
11204
11328
|
}
|
|
11205
11329
|
readSnapshot(taskId) {
|
|
11206
11330
|
try {
|
|
11207
|
-
const value = JSON.parse(
|
|
11331
|
+
const value = JSON.parse(fs10.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11208
11332
|
if (!isObject4(value) || value.v !== 1 || value.task !== taskId || typeof value.seq !== "number" || typeof value.lastId !== "string" || typeof value.lastStart !== "number" || typeof value.end !== "number" || !isObject4(value.record))
|
|
11209
11333
|
return null;
|
|
11210
11334
|
return value;
|
|
@@ -11306,8 +11430,8 @@ class TaskStore {
|
|
|
11306
11430
|
}
|
|
11307
11431
|
}
|
|
11308
11432
|
openTask(record, type, key, legacy = null) {
|
|
11309
|
-
const dir =
|
|
11310
|
-
|
|
11433
|
+
const dir = path9.join(this.paths().tasks, record.id);
|
|
11434
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
11311
11435
|
const blobs = this.blobStore();
|
|
11312
11436
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
11313
11437
|
const event = this.event(record.id, 1, type, {
|
|
@@ -11359,11 +11483,11 @@ class TaskStore {
|
|
|
11359
11483
|
} catch {}
|
|
11360
11484
|
if (!latest || latest.events[0]?.id !== last.id)
|
|
11361
11485
|
return failure2("busy", "the task log changed during compaction; retry", { taskId });
|
|
11362
|
-
const appended =
|
|
11486
|
+
const appended = fs10.readFileSync(file).subarray(log.end, latest.end);
|
|
11363
11487
|
const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
|
|
11364
11488
|
replaceFile(file, bytes.toString("utf8"), true);
|
|
11365
11489
|
try {
|
|
11366
|
-
|
|
11490
|
+
fs10.rmSync(this.snapshotPath(taskId), { force: true });
|
|
11367
11491
|
} catch {}
|
|
11368
11492
|
const reloaded = this.load(taskId);
|
|
11369
11493
|
if (reloaded.ok && reloaded.data)
|
|
@@ -11376,7 +11500,7 @@ class TaskStore {
|
|
|
11376
11500
|
}
|
|
11377
11501
|
countEvents(file) {
|
|
11378
11502
|
try {
|
|
11379
|
-
const bytes =
|
|
11503
|
+
const bytes = fs10.readFileSync(file);
|
|
11380
11504
|
let count = 0;
|
|
11381
11505
|
for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
|
|
11382
11506
|
count += 1;
|
|
@@ -11385,12 +11509,12 @@ class TaskStore {
|
|
|
11385
11509
|
return 0;
|
|
11386
11510
|
}
|
|
11387
11511
|
}
|
|
11388
|
-
openTasksForKey(key) {
|
|
11512
|
+
openTasksForKey(key, known) {
|
|
11389
11513
|
const all = this.listStoreIndex();
|
|
11390
11514
|
if (!all.ok)
|
|
11391
11515
|
return all;
|
|
11392
|
-
const location = this.location();
|
|
11393
|
-
const renames = location
|
|
11516
|
+
const location = known ? null : this.location();
|
|
11517
|
+
const renames = known ?? (location?.ok ? branchRenames(location.data) : []);
|
|
11394
11518
|
return success(null, null, all.data.filter((entry) => entry.key !== null && entry.status !== "closed").filter((entry) => (renames.length ? followRenames(entry.key, entry.boundAt ?? entry.createdAt, renames) : entry.key) === key).toSorted(oldestFirst));
|
|
11395
11519
|
}
|
|
11396
11520
|
openTaskForKey(key) {
|
|
@@ -11398,8 +11522,8 @@ class TaskStore {
|
|
|
11398
11522
|
return bound.ok ? success(null, null, bound.data[0] ?? null) : bound;
|
|
11399
11523
|
}
|
|
11400
11524
|
withKeyLock(key, operation) {
|
|
11401
|
-
const digest =
|
|
11402
|
-
return this.withLock(
|
|
11525
|
+
const digest = createHash6("sha256").update(key).digest("hex").slice(0, 32);
|
|
11526
|
+
return this.withLock(path9.join(this.paths().dir, "keys", `${digest}.lock`), operation);
|
|
11403
11527
|
}
|
|
11404
11528
|
bindLocked(taskId, key, timestamp) {
|
|
11405
11529
|
const current = this.readWorkspace();
|
|
@@ -11540,7 +11664,7 @@ class TaskStore {
|
|
|
11540
11664
|
writeWorkspace(record) {
|
|
11541
11665
|
const file = this.paths().workspace;
|
|
11542
11666
|
try {
|
|
11543
|
-
|
|
11667
|
+
fs10.mkdirSync(path9.dirname(file), { recursive: true });
|
|
11544
11668
|
replaceFile(file, `${canonicalJson(record)}
|
|
11545
11669
|
`, true);
|
|
11546
11670
|
return success(null, null, null);
|
|
@@ -11550,7 +11674,7 @@ class TaskStore {
|
|
|
11550
11674
|
}
|
|
11551
11675
|
readIndex() {
|
|
11552
11676
|
try {
|
|
11553
|
-
const value = JSON.parse(
|
|
11677
|
+
const value = JSON.parse(fs10.readFileSync(this.paths().index, "utf8"));
|
|
11554
11678
|
if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
|
|
11555
11679
|
return {};
|
|
11556
11680
|
const tasks = {};
|
|
@@ -11564,7 +11688,7 @@ class TaskStore {
|
|
|
11564
11688
|
}
|
|
11565
11689
|
writeIndex(entries) {
|
|
11566
11690
|
try {
|
|
11567
|
-
|
|
11691
|
+
fs10.mkdirSync(this.paths().tasks, { recursive: true });
|
|
11568
11692
|
replaceFile(this.paths().index, JSON.stringify({
|
|
11569
11693
|
version: INDEX_VERSION,
|
|
11570
11694
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
@@ -11618,23 +11742,23 @@ class TaskStore {
|
|
|
11618
11742
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
11619
11743
|
}
|
|
11620
11744
|
v2Dirs() {
|
|
11621
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
11745
|
+
return [...new Set([this.root, this.origin])].map((dir) => path9.join(dir, ".workit"));
|
|
11622
11746
|
}
|
|
11623
11747
|
pendingMigrations() {
|
|
11624
11748
|
return this.v2Dirs().filter((dir) => {
|
|
11625
|
-
const raw = this.readV2Workspace(
|
|
11749
|
+
const raw = this.readV2Workspace(path9.join(dir, "workspace.json"));
|
|
11626
11750
|
return raw !== null && raw !== TOMBSTONE;
|
|
11627
11751
|
});
|
|
11628
11752
|
}
|
|
11629
11753
|
migrateV2(v2) {
|
|
11630
11754
|
const dirs = this.paths();
|
|
11631
|
-
const v2Workspace =
|
|
11632
|
-
const v2Tasks =
|
|
11633
|
-
const legacy =
|
|
11755
|
+
const v2Workspace = path9.join(v2, "workspace.json");
|
|
11756
|
+
const v2Tasks = path9.join(v2, "tasks");
|
|
11757
|
+
const legacy = path9.join(dirs.dir, "legacy", checkoutSlug(path9.dirname(v2)), "v2");
|
|
11634
11758
|
const raw = this.readV2Workspace(v2Workspace);
|
|
11635
11759
|
if (raw === null || raw === TOMBSTONE)
|
|
11636
11760
|
return success(null, null, null);
|
|
11637
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
11761
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path9.join(v2, "metadata.lock"), () => {
|
|
11638
11762
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
11639
11763
|
if (workspace === null || workspace === TOMBSTONE)
|
|
11640
11764
|
return success(null, null, null);
|
|
@@ -11643,7 +11767,7 @@ class TaskStore {
|
|
|
11643
11767
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
11644
11768
|
let files = [];
|
|
11645
11769
|
try {
|
|
11646
|
-
files =
|
|
11770
|
+
files = fs10.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path9.join(v2Tasks, name)));
|
|
11647
11771
|
} catch {}
|
|
11648
11772
|
const current = this.readWorkspace();
|
|
11649
11773
|
if (!current.ok)
|
|
@@ -11661,22 +11785,22 @@ class TaskStore {
|
|
|
11661
11785
|
let skipped = 0;
|
|
11662
11786
|
const done = new Map;
|
|
11663
11787
|
try {
|
|
11664
|
-
|
|
11788
|
+
fs10.mkdirSync(path9.join(legacy, "tasks"), { recursive: true });
|
|
11665
11789
|
for (const name of ["workspace.json", "index.json"])
|
|
11666
|
-
if (
|
|
11667
|
-
|
|
11790
|
+
if (fs10.existsSync(path9.join(v2, name)))
|
|
11791
|
+
fs10.copyFileSync(path9.join(v2, name), path9.join(legacy, name));
|
|
11668
11792
|
for (const name of files) {
|
|
11669
11793
|
const id = name.slice(0, -5);
|
|
11670
|
-
const file =
|
|
11671
|
-
const bytes =
|
|
11794
|
+
const file = path9.join(v2Tasks, name);
|
|
11795
|
+
const bytes = fs10.readFileSync(file, "utf8");
|
|
11672
11796
|
const digest = sha256(bytes);
|
|
11673
|
-
|
|
11797
|
+
fs10.writeFileSync(path9.join(legacy, "tasks", name), bytes);
|
|
11674
11798
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
11675
11799
|
if (!parsed.ok || parsed.data.id !== id)
|
|
11676
11800
|
return failure2("recovery_required", `cannot migrate 2.x task ${id}: ${parsed.ok ? "file name and record ID differ" : parsed.error}; move ${file} aside to continue`, { path: file });
|
|
11677
11801
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
11678
|
-
const origin = { path:
|
|
11679
|
-
if (!
|
|
11802
|
+
const origin = { path: path9.join(legacy, "tasks", name), digest };
|
|
11803
|
+
if (!fs10.existsSync(this.eventsPath(id))) {
|
|
11680
11804
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
11681
11805
|
migrated += 1;
|
|
11682
11806
|
} else {
|
|
@@ -11711,8 +11835,8 @@ class TaskStore {
|
|
|
11711
11835
|
try {
|
|
11712
11836
|
let complete = true;
|
|
11713
11837
|
for (const [name, digest] of done) {
|
|
11714
|
-
const file =
|
|
11715
|
-
if (sha256(
|
|
11838
|
+
const file = path9.join(v2Tasks, name);
|
|
11839
|
+
if (sha256(fs10.readFileSync(file, "utf8")) !== digest) {
|
|
11716
11840
|
complete = false;
|
|
11717
11841
|
continue;
|
|
11718
11842
|
}
|
|
@@ -11722,7 +11846,7 @@ class TaskStore {
|
|
|
11722
11846
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
11723
11847
|
path: v2
|
|
11724
11848
|
});
|
|
11725
|
-
|
|
11849
|
+
fs10.rmSync(path9.join(v2, "index.json"), { force: true });
|
|
11726
11850
|
this.writeTombstone(v2, target.id);
|
|
11727
11851
|
} catch (error) {
|
|
11728
11852
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -11742,45 +11866,45 @@ class TaskStore {
|
|
|
11742
11866
|
}
|
|
11743
11867
|
adoptLocalStore() {
|
|
11744
11868
|
const dirs = this.paths();
|
|
11745
|
-
const marker =
|
|
11746
|
-
if (!dirs.shared || !
|
|
11869
|
+
const marker = path9.join(dirs.v2, "store.json");
|
|
11870
|
+
if (!dirs.shared || !fs10.existsSync(marker))
|
|
11747
11871
|
return success(null, null, null);
|
|
11748
11872
|
return this.withLock(dirs.checkoutLock, () => {
|
|
11749
|
-
if (!
|
|
11873
|
+
if (!fs10.existsSync(marker))
|
|
11750
11874
|
return success(null, null, null);
|
|
11751
11875
|
let tasks = 0;
|
|
11752
11876
|
try {
|
|
11753
11877
|
const merge = (from, to) => {
|
|
11754
|
-
if (!
|
|
11755
|
-
|
|
11878
|
+
if (!fs10.existsSync(to)) {
|
|
11879
|
+
fs10.mkdirSync(path9.dirname(to), { recursive: true });
|
|
11756
11880
|
try {
|
|
11757
|
-
|
|
11881
|
+
fs10.renameSync(from, to);
|
|
11758
11882
|
} catch {
|
|
11759
|
-
|
|
11760
|
-
|
|
11883
|
+
fs10.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
11884
|
+
fs10.rmSync(from, { recursive: true, force: true });
|
|
11761
11885
|
}
|
|
11762
11886
|
return;
|
|
11763
11887
|
}
|
|
11764
|
-
if (
|
|
11765
|
-
for (const name of
|
|
11766
|
-
merge(
|
|
11888
|
+
if (fs10.lstatSync(from).isDirectory() && fs10.lstatSync(to).isDirectory())
|
|
11889
|
+
for (const name of fs10.readdirSync(from))
|
|
11890
|
+
merge(path9.join(from, name), path9.join(to, name));
|
|
11767
11891
|
};
|
|
11768
11892
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
11769
|
-
const from =
|
|
11770
|
-
if (!
|
|
11893
|
+
const from = path9.join(dirs.v2, sub);
|
|
11894
|
+
if (!fs10.existsSync(from))
|
|
11771
11895
|
continue;
|
|
11772
11896
|
if (sub === "tasks") {
|
|
11773
|
-
for (const name of
|
|
11774
|
-
if (validId(name) && !
|
|
11897
|
+
for (const name of fs10.readdirSync(from))
|
|
11898
|
+
if (validId(name) && !fs10.existsSync(path9.join(dirs.dir, sub, name)))
|
|
11775
11899
|
tasks += 1;
|
|
11776
11900
|
}
|
|
11777
|
-
merge(from,
|
|
11901
|
+
merge(from, path9.join(dirs.dir, sub));
|
|
11778
11902
|
}
|
|
11779
|
-
const ledger =
|
|
11780
|
-
if (
|
|
11781
|
-
merge(ledger,
|
|
11782
|
-
|
|
11783
|
-
|
|
11903
|
+
const ledger = path9.join(dirs.v2, "ledger");
|
|
11904
|
+
if (fs10.existsSync(ledger) && !fs10.existsSync(path9.join(dirs.dir, "ledger")))
|
|
11905
|
+
merge(ledger, path9.join(dirs.dir, "ledger"));
|
|
11906
|
+
fs10.rmSync(path9.join(dirs.v2, "task-index.json"), { force: true });
|
|
11907
|
+
fs10.renameSync(marker, path9.join(dirs.v2, "store.moved.json"));
|
|
11784
11908
|
} catch (error) {
|
|
11785
11909
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
11786
11910
|
path: dirs.v2
|
|
@@ -11800,7 +11924,7 @@ class TaskStore {
|
|
|
11800
11924
|
readV2Workspace(file) {
|
|
11801
11925
|
let bytes;
|
|
11802
11926
|
try {
|
|
11803
|
-
bytes =
|
|
11927
|
+
bytes = fs10.readFileSync(file, "utf8");
|
|
11804
11928
|
} catch {
|
|
11805
11929
|
return null;
|
|
11806
11930
|
}
|
|
@@ -11818,7 +11942,7 @@ class TaskStore {
|
|
|
11818
11942
|
schemaVersion: SCHEMA_VERSION,
|
|
11819
11943
|
id: workspaceId ?? newId(),
|
|
11820
11944
|
revision: newRevision(),
|
|
11821
|
-
root:
|
|
11945
|
+
root: path9.dirname(v2),
|
|
11822
11946
|
runtime: { createdWith: version, updatedWith: version },
|
|
11823
11947
|
writer: null,
|
|
11824
11948
|
store: {
|
|
@@ -11829,12 +11953,12 @@ class TaskStore {
|
|
|
11829
11953
|
},
|
|
11830
11954
|
critical: ["store"]
|
|
11831
11955
|
};
|
|
11832
|
-
|
|
11833
|
-
const ignore =
|
|
11834
|
-
if (!
|
|
11835
|
-
|
|
11956
|
+
fs10.mkdirSync(v2, { recursive: true });
|
|
11957
|
+
const ignore = path9.join(v2, ".gitignore");
|
|
11958
|
+
if (!fs10.existsSync(ignore))
|
|
11959
|
+
fs10.writeFileSync(ignore, `*
|
|
11836
11960
|
`);
|
|
11837
|
-
replaceFile(
|
|
11961
|
+
replaceFile(path9.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
11838
11962
|
`, true);
|
|
11839
11963
|
}
|
|
11840
11964
|
stubFor(taskId) {
|
|
@@ -11850,7 +11974,7 @@ class TaskStore {
|
|
|
11850
11974
|
}
|
|
11851
11975
|
isStub(file) {
|
|
11852
11976
|
try {
|
|
11853
|
-
const value = JSON.parse(
|
|
11977
|
+
const value = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
11854
11978
|
return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
|
|
11855
11979
|
} catch {
|
|
11856
11980
|
return false;
|
|
@@ -11858,32 +11982,32 @@ class TaskStore {
|
|
|
11858
11982
|
}
|
|
11859
11983
|
writeTombstoneIfMissing() {
|
|
11860
11984
|
try {
|
|
11861
|
-
const v2 =
|
|
11862
|
-
if (
|
|
11985
|
+
const v2 = path9.join(this.root, ".workit");
|
|
11986
|
+
if (fs10.existsSync(path9.join(v2, "workspace.json")))
|
|
11863
11987
|
return;
|
|
11864
11988
|
let id = null;
|
|
11865
11989
|
try {
|
|
11866
|
-
id = JSON.parse(
|
|
11990
|
+
id = JSON.parse(fs10.readFileSync(this.paths().workspace, "utf8")).id ?? null;
|
|
11867
11991
|
} catch {}
|
|
11868
11992
|
this.writeTombstone(v2, id);
|
|
11869
11993
|
} catch {}
|
|
11870
11994
|
}
|
|
11871
11995
|
initializeStorage() {
|
|
11872
11996
|
const dirs = this.paths();
|
|
11873
|
-
retryTransient(() =>
|
|
11874
|
-
retryTransient(() =>
|
|
11875
|
-
if (!
|
|
11997
|
+
retryTransient(() => fs10.mkdirSync(dirs.checkout, { recursive: true }));
|
|
11998
|
+
retryTransient(() => fs10.mkdirSync(dirs.tasks, { recursive: true }));
|
|
11999
|
+
if (!fs10.existsSync(dirs.marker))
|
|
11876
12000
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
11877
12001
|
`, false);
|
|
11878
12002
|
if (!dirs.shared) {
|
|
11879
|
-
const ignore =
|
|
12003
|
+
const ignore = path9.join(dirs.dir, ".gitignore");
|
|
11880
12004
|
let current = null;
|
|
11881
12005
|
try {
|
|
11882
|
-
current =
|
|
12006
|
+
current = fs10.readFileSync(ignore, "utf8");
|
|
11883
12007
|
} catch {}
|
|
11884
12008
|
if (current !== `*
|
|
11885
12009
|
`)
|
|
11886
|
-
retryTransient(() =>
|
|
12010
|
+
retryTransient(() => fs10.writeFileSync(ignore, `*
|
|
11887
12011
|
`));
|
|
11888
12012
|
}
|
|
11889
12013
|
this.writeTombstoneIfMissing();
|
|
@@ -11899,7 +12023,7 @@ class TaskStore {
|
|
|
11899
12023
|
shouldReclaim: ({ payload, nowMs }) => {
|
|
11900
12024
|
let ageMs = null;
|
|
11901
12025
|
try {
|
|
11902
|
-
ageMs = nowMs -
|
|
12026
|
+
ageMs = nowMs - fs10.lstatSync(lockPath).mtimeMs;
|
|
11903
12027
|
} catch {}
|
|
11904
12028
|
return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
|
|
11905
12029
|
},
|
|
@@ -11936,7 +12060,7 @@ class TaskStore {
|
|
|
11936
12060
|
withLock(lockPath, operation) {
|
|
11937
12061
|
try {
|
|
11938
12062
|
this.initializeStorage();
|
|
11939
|
-
|
|
12063
|
+
fs10.mkdirSync(path9.dirname(lockPath), { recursive: true });
|
|
11940
12064
|
} catch (error) {
|
|
11941
12065
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
11942
12066
|
path: this.paths().dir
|
|
@@ -11976,18 +12100,18 @@ class TaskStore {
|
|
|
11976
12100
|
lockDirWritable(lockPath) {
|
|
11977
12101
|
const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
|
|
11978
12102
|
try {
|
|
11979
|
-
|
|
12103
|
+
fs10.closeSync(fs10.openSync(probe, "wx", 384));
|
|
11980
12104
|
} catch {
|
|
11981
12105
|
return false;
|
|
11982
12106
|
}
|
|
11983
12107
|
try {
|
|
11984
|
-
|
|
12108
|
+
fs10.rmSync(probe, { force: true });
|
|
11985
12109
|
} catch {}
|
|
11986
12110
|
return true;
|
|
11987
12111
|
}
|
|
11988
12112
|
lockHolderPresent(lockPath) {
|
|
11989
12113
|
try {
|
|
11990
|
-
|
|
12114
|
+
fs10.lstatSync(lockPath);
|
|
11991
12115
|
return true;
|
|
11992
12116
|
} catch (error) {
|
|
11993
12117
|
return error?.code !== "ENOENT";
|
|
@@ -11999,12 +12123,12 @@ class TaskStore {
|
|
|
11999
12123
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
12000
12124
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
12001
12125
|
path: lockPath,
|
|
12002
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
12126
|
+
guidance: `Check permissions and the read-only attribute on ${path9.dirname(lockPath)}.`
|
|
12003
12127
|
});
|
|
12004
12128
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
12005
12129
|
let lock = null;
|
|
12006
12130
|
try {
|
|
12007
|
-
lock = parseMetadataLockOrNull(
|
|
12131
|
+
lock = parseMetadataLockOrNull(fs10.readFileSync(lockPath, "utf8"));
|
|
12008
12132
|
} catch {}
|
|
12009
12133
|
return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
12010
12134
|
outcome: "not_started",
|
|
@@ -12019,17 +12143,17 @@ class TaskStore {
|
|
|
12019
12143
|
|
|
12020
12144
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12021
12145
|
import { existsSync as existsSync4 } from "node:fs";
|
|
12022
|
-
import
|
|
12146
|
+
import path14 from "node:path";
|
|
12023
12147
|
|
|
12024
12148
|
// packages/workit-core/src/ledger.ts
|
|
12025
12149
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12026
|
-
import { createHash as
|
|
12027
|
-
import
|
|
12028
|
-
import
|
|
12150
|
+
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
12151
|
+
import fs12 from "node:fs";
|
|
12152
|
+
import path13 from "node:path";
|
|
12029
12153
|
|
|
12030
12154
|
// packages/workit-core/src/core/vcs-config.ts
|
|
12031
|
-
import
|
|
12032
|
-
import
|
|
12155
|
+
import fs11 from "node:fs";
|
|
12156
|
+
import path12 from "node:path";
|
|
12033
12157
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12034
12158
|
|
|
12035
12159
|
// packages/workit-core/src/core/config.ts
|
|
@@ -12043,7 +12167,7 @@ import {
|
|
|
12043
12167
|
writeFileSync as writeFileSync2
|
|
12044
12168
|
} from "node:fs";
|
|
12045
12169
|
import os2 from "node:os";
|
|
12046
|
-
import
|
|
12170
|
+
import path10 from "node:path";
|
|
12047
12171
|
|
|
12048
12172
|
// packages/workit-core/src/core/boundary.ts
|
|
12049
12173
|
var EVENT = {
|
|
@@ -12087,7 +12211,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
12087
12211
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
12088
12212
|
};
|
|
12089
12213
|
};
|
|
12090
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
12214
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workit");
|
|
12091
12215
|
var migratedDir = null;
|
|
12092
12216
|
var migrationFailed = false;
|
|
12093
12217
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -12097,7 +12221,7 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12097
12221
|
migratedDir = dir;
|
|
12098
12222
|
return dir;
|
|
12099
12223
|
}
|
|
12100
|
-
const legacy =
|
|
12224
|
+
const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
12101
12225
|
if (!existsSync3(legacy)) {
|
|
12102
12226
|
migratedDir = dir;
|
|
12103
12227
|
return dir;
|
|
@@ -12110,8 +12234,8 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12110
12234
|
mkdirSync2(dir, { recursive: true });
|
|
12111
12235
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
12112
12236
|
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
12113
|
-
const src =
|
|
12114
|
-
const dest =
|
|
12237
|
+
const src = path10.join(legacy, entry.name);
|
|
12238
|
+
const dest = path10.join(dir, entry.name);
|
|
12115
12239
|
if (existsSync3(dest))
|
|
12116
12240
|
continue;
|
|
12117
12241
|
try {
|
|
@@ -12183,7 +12307,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
12183
12307
|
return { status: "valid", path: file, config };
|
|
12184
12308
|
};
|
|
12185
12309
|
var readConfigTyped = (dir) => {
|
|
12186
|
-
const file =
|
|
12310
|
+
const file = path10.join(dir ?? configDir(), "config.json");
|
|
12187
12311
|
return parseConfigResult(readSafe(file), file);
|
|
12188
12312
|
};
|
|
12189
12313
|
var readConfig = () => {
|
|
@@ -12192,7 +12316,7 @@ var readConfig = () => {
|
|
|
12192
12316
|
throw new Error(result.error);
|
|
12193
12317
|
return result.config ?? DEFAULTS;
|
|
12194
12318
|
};
|
|
12195
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
12319
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
12196
12320
|
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
12197
12321
|
var resolveBranchPolicy = (config, workspace, repoPreset) => {
|
|
12198
12322
|
const wp = workspace?.branchPolicy ?? {};
|
|
@@ -12234,9 +12358,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
12234
12358
|
|
|
12235
12359
|
// packages/workit-core/src/core/workspaces.ts
|
|
12236
12360
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
12237
|
-
import { createHash as
|
|
12361
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12238
12362
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
12239
|
-
import
|
|
12363
|
+
import path11 from "node:path";
|
|
12240
12364
|
|
|
12241
12365
|
// packages/workit-core/src/core/branch-policy.ts
|
|
12242
12366
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -12361,7 +12485,7 @@ var releaseTrackSchema = object({
|
|
|
12361
12485
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
12362
12486
|
}
|
|
12363
12487
|
}
|
|
12364
|
-
if (track.versionSource?.kind === "package-json" && (
|
|
12488
|
+
if (track.versionSource?.kind === "package-json" && (path11.posix.isAbsolute(track.versionSource.path.replaceAll("\\", "/")) || /^[A-Za-z]:\//u.test(track.versionSource.path.replaceAll("\\", "/")) || track.versionSource.path.replaceAll("\\", "/").split("/").includes(".."))) {
|
|
12365
12489
|
context.addIssue({
|
|
12366
12490
|
code: "custom",
|
|
12367
12491
|
path: ["versionSource", "path"],
|
|
@@ -12421,7 +12545,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
12421
12545
|
names.add(workspace.name);
|
|
12422
12546
|
}
|
|
12423
12547
|
});
|
|
12424
|
-
var workspacesRevision = (contents) =>
|
|
12548
|
+
var workspacesRevision = (contents) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
|
|
12425
12549
|
var validateWorkspacesDocument = (parsed, file) => {
|
|
12426
12550
|
if (!isConfigObject(parsed)) {
|
|
12427
12551
|
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
@@ -12459,7 +12583,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
12459
12583
|
};
|
|
12460
12584
|
};
|
|
12461
12585
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
12462
|
-
const file =
|
|
12586
|
+
const file = path11.join(dir, "workspaces.json");
|
|
12463
12587
|
let raw;
|
|
12464
12588
|
try {
|
|
12465
12589
|
raw = readFileSync3(file, "utf8");
|
|
@@ -12570,9 +12694,9 @@ var mainCheckoutOf = (cwd) => {
|
|
|
12570
12694
|
let main = null;
|
|
12571
12695
|
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
12572
12696
|
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
12573
|
-
if (gitDir && common &&
|
|
12697
|
+
if (gitDir && common && path11.resolve(gitDir) !== path11.resolve(common)) {
|
|
12574
12698
|
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
12575
|
-
main = listed?.trim() || (
|
|
12699
|
+
main = listed?.trim() || (path11.basename(common) === ".git" ? path11.dirname(common) : common);
|
|
12576
12700
|
}
|
|
12577
12701
|
mainCheckouts.set(cwd, main);
|
|
12578
12702
|
return main;
|
|
@@ -12663,7 +12787,7 @@ var runtimeWorkspaceIndexSchema = object({
|
|
|
12663
12787
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
12664
12788
|
}).passthrough();
|
|
12665
12789
|
var matchRuntimeWorkspace = (cwd) => {
|
|
12666
|
-
const file =
|
|
12790
|
+
const file = path11.join(configDir(), "workspaces.json");
|
|
12667
12791
|
let raw;
|
|
12668
12792
|
try {
|
|
12669
12793
|
raw = readFileSync3(file, "utf8");
|
|
@@ -12738,7 +12862,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
|
|
|
12738
12862
|
}
|
|
12739
12863
|
|
|
12740
12864
|
// packages/workit-core/src/core/release-tracks.ts
|
|
12741
|
-
import { createHash as
|
|
12865
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
12742
12866
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
12743
12867
|
class ReleaseTrackError extends Error {
|
|
12744
12868
|
}
|
|
@@ -13121,7 +13245,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13121
13245
|
]) ?? "",
|
|
13122
13246
|
git3(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
13123
13247
|
] : [];
|
|
13124
|
-
key =
|
|
13248
|
+
key = createHash8("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
13125
13249
|
const hit = memo2.get(key);
|
|
13126
13250
|
if (hit)
|
|
13127
13251
|
return hit;
|
|
@@ -13136,7 +13260,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13136
13260
|
}
|
|
13137
13261
|
|
|
13138
13262
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13139
|
-
var workspacesPath = () =>
|
|
13263
|
+
var workspacesPath = () => path12.join(configDir(), "workspaces.json");
|
|
13140
13264
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13141
13265
|
var remoteProvider = (cwd) => {
|
|
13142
13266
|
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -13153,7 +13277,7 @@ var readVcsConfig = () => {
|
|
|
13153
13277
|
const file = vcsConfigPath();
|
|
13154
13278
|
let raw;
|
|
13155
13279
|
try {
|
|
13156
|
-
raw =
|
|
13280
|
+
raw = fs11.readFileSync(file, "utf8");
|
|
13157
13281
|
} catch {
|
|
13158
13282
|
return { status: "missing", path: file, config: {} };
|
|
13159
13283
|
}
|
|
@@ -13238,7 +13362,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
13238
13362
|
const prov = cfg[provider] ?? {};
|
|
13239
13363
|
const out = {
|
|
13240
13364
|
ok: true,
|
|
13241
|
-
configPath:
|
|
13365
|
+
configPath: path12.resolve(cfgPath),
|
|
13242
13366
|
provider,
|
|
13243
13367
|
defaultTargetBranch: workspaceDefault,
|
|
13244
13368
|
pr: cfg.pr ?? {},
|
|
@@ -13364,7 +13488,7 @@ function diffHash(cwd, base, head) {
|
|
|
13364
13488
|
], GIT_TIMEOUTS.worktree);
|
|
13365
13489
|
if (!diff.ok || !diff.stdout)
|
|
13366
13490
|
return null;
|
|
13367
|
-
const hash =
|
|
13491
|
+
const hash = createHash9("sha256");
|
|
13368
13492
|
for (const line of diff.stdout.split(`
|
|
13369
13493
|
`)) {
|
|
13370
13494
|
if (line.startsWith("index "))
|
|
@@ -13376,14 +13500,14 @@ function diffHash(cwd, base, head) {
|
|
|
13376
13500
|
return `sha256:${hash.digest("hex")}`;
|
|
13377
13501
|
}
|
|
13378
13502
|
function storeRoot(cwd) {
|
|
13379
|
-
const location = resolveStore(
|
|
13503
|
+
const location = resolveStore(path13.resolve(cwd));
|
|
13380
13504
|
if (location instanceof Error)
|
|
13381
13505
|
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
13382
13506
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13383
13507
|
}
|
|
13384
13508
|
function ledgerPath(cwd) {
|
|
13385
13509
|
const root = storeRoot(cwd);
|
|
13386
|
-
return root.ok ? { ok: true, value:
|
|
13510
|
+
return root.ok ? { ok: true, value: path13.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13387
13511
|
}
|
|
13388
13512
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
|
|
13389
13513
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -13403,7 +13527,7 @@ var onNetworkFs = (dir) => {
|
|
|
13403
13527
|
if (process.platform !== "linux")
|
|
13404
13528
|
return false;
|
|
13405
13529
|
try {
|
|
13406
|
-
return isNetworkFsType(
|
|
13530
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
13407
13531
|
} catch {
|
|
13408
13532
|
return false;
|
|
13409
13533
|
}
|
|
@@ -13413,15 +13537,15 @@ var LOCK_STALE_MS = 30000;
|
|
|
13413
13537
|
var LOCK_OWNER = "owner";
|
|
13414
13538
|
var lockToken = (lock) => {
|
|
13415
13539
|
try {
|
|
13416
|
-
return
|
|
13540
|
+
return fs12.readFileSync(path13.join(lock, LOCK_OWNER), "utf8");
|
|
13417
13541
|
} catch {
|
|
13418
13542
|
return null;
|
|
13419
13543
|
}
|
|
13420
13544
|
};
|
|
13421
13545
|
var lockAgeMs = (lock) => {
|
|
13422
|
-
for (const target of [
|
|
13546
|
+
for (const target of [path13.join(lock, LOCK_OWNER), lock]) {
|
|
13423
13547
|
try {
|
|
13424
|
-
return Date.now() -
|
|
13548
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
13425
13549
|
} catch {}
|
|
13426
13550
|
}
|
|
13427
13551
|
return null;
|
|
@@ -13429,14 +13553,14 @@ var lockAgeMs = (lock) => {
|
|
|
13429
13553
|
var ledgerLock = {
|
|
13430
13554
|
acquire(lock) {
|
|
13431
13555
|
try {
|
|
13432
|
-
|
|
13556
|
+
fs12.mkdirSync(lock);
|
|
13433
13557
|
} catch (error) {
|
|
13434
13558
|
if (error.code === "EEXIST")
|
|
13435
13559
|
return null;
|
|
13436
13560
|
throw error;
|
|
13437
13561
|
}
|
|
13438
13562
|
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
13439
|
-
|
|
13563
|
+
fs12.writeFileSync(path13.join(lock, LOCK_OWNER), token);
|
|
13440
13564
|
return token;
|
|
13441
13565
|
},
|
|
13442
13566
|
release(lock, token) {
|
|
@@ -13444,35 +13568,35 @@ var ledgerLock = {
|
|
|
13444
13568
|
return false;
|
|
13445
13569
|
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
13446
13570
|
try {
|
|
13447
|
-
|
|
13571
|
+
fs12.renameSync(lock, tomb);
|
|
13448
13572
|
} catch {
|
|
13449
13573
|
return false;
|
|
13450
13574
|
}
|
|
13451
13575
|
if (lockToken(tomb) !== token) {
|
|
13452
13576
|
try {
|
|
13453
|
-
|
|
13577
|
+
fs12.renameSync(tomb, lock);
|
|
13454
13578
|
} catch {}
|
|
13455
13579
|
return false;
|
|
13456
13580
|
}
|
|
13457
|
-
|
|
13581
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13458
13582
|
return true;
|
|
13459
13583
|
},
|
|
13460
13584
|
token: lockToken,
|
|
13461
13585
|
reap(lock, observed) {
|
|
13462
13586
|
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
13463
13587
|
try {
|
|
13464
|
-
|
|
13588
|
+
fs12.renameSync(lock, tomb);
|
|
13465
13589
|
} catch {
|
|
13466
13590
|
return false;
|
|
13467
13591
|
}
|
|
13468
13592
|
if (lockToken(tomb) === observed) {
|
|
13469
|
-
|
|
13593
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13470
13594
|
return true;
|
|
13471
13595
|
}
|
|
13472
13596
|
try {
|
|
13473
|
-
|
|
13597
|
+
fs12.renameSync(tomb, lock);
|
|
13474
13598
|
} catch {
|
|
13475
|
-
|
|
13599
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13476
13600
|
}
|
|
13477
13601
|
return false;
|
|
13478
13602
|
}
|
|
@@ -13499,14 +13623,14 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
|
13499
13623
|
ledgerLock.release(lock, token);
|
|
13500
13624
|
}
|
|
13501
13625
|
}
|
|
13502
|
-
var sha2562 = (text) =>
|
|
13626
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
13503
13627
|
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
13504
13628
|
${body}`);
|
|
13505
13629
|
var TAIL_BYTES = 64 * 1024;
|
|
13506
13630
|
var lastLine = (fd, size) => {
|
|
13507
13631
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
13508
13632
|
const buffer = Buffer.alloc(span);
|
|
13509
|
-
|
|
13633
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
13510
13634
|
const end = buffer.lastIndexOf(10);
|
|
13511
13635
|
if (end < 0) {
|
|
13512
13636
|
if (span === size)
|
|
@@ -13537,13 +13661,13 @@ var lastLine = (fd, size) => {
|
|
|
13537
13661
|
};
|
|
13538
13662
|
function appendLine(file, build, options) {
|
|
13539
13663
|
const write = () => {
|
|
13540
|
-
const fd =
|
|
13664
|
+
const fd = fs12.openSync(file, "a+");
|
|
13541
13665
|
try {
|
|
13542
|
-
const size =
|
|
13666
|
+
const size = fs12.fstatSync(fd).size;
|
|
13543
13667
|
let fence = "";
|
|
13544
13668
|
if (size > 0) {
|
|
13545
13669
|
const last = Buffer.alloc(1);
|
|
13546
|
-
|
|
13670
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
13547
13671
|
if (last[0] !== 10)
|
|
13548
13672
|
fence = `
|
|
13549
13673
|
`;
|
|
@@ -13552,17 +13676,17 @@ function appendLine(file, build, options) {
|
|
|
13552
13676
|
if (!line.ok)
|
|
13553
13677
|
return line;
|
|
13554
13678
|
const bytes = Buffer.from(fence + line.value);
|
|
13555
|
-
const written =
|
|
13679
|
+
const written = fs12.writeSync(fd, bytes);
|
|
13556
13680
|
if (written !== bytes.length)
|
|
13557
13681
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
13558
13682
|
return { ok: true, value: undefined };
|
|
13559
13683
|
} finally {
|
|
13560
|
-
|
|
13684
|
+
fs12.closeSync(fd);
|
|
13561
13685
|
}
|
|
13562
13686
|
};
|
|
13563
13687
|
try {
|
|
13564
|
-
|
|
13565
|
-
if (!options.forceLock && !onNetworkFs(
|
|
13688
|
+
fs12.mkdirSync(path13.dirname(file), { recursive: true });
|
|
13689
|
+
if (!options.forceLock && !onNetworkFs(path13.dirname(file)))
|
|
13566
13690
|
return write();
|
|
13567
13691
|
const locked = withLedgerLock(file, write);
|
|
13568
13692
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -13634,7 +13758,7 @@ function readLedger(cwd, options = {}) {
|
|
|
13634
13758
|
return file;
|
|
13635
13759
|
let text = "";
|
|
13636
13760
|
try {
|
|
13637
|
-
text =
|
|
13761
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
13638
13762
|
} catch (error) {
|
|
13639
13763
|
if (error.code === "ENOENT")
|
|
13640
13764
|
return {
|
|
@@ -13828,11 +13952,20 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
|
|
|
13828
13952
|
sessions.add(session);
|
|
13829
13953
|
return sessions;
|
|
13830
13954
|
}
|
|
13955
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
13956
|
+
var authoredBy = (authors, session) => {
|
|
13957
|
+
if (authors.has(session))
|
|
13958
|
+
return true;
|
|
13959
|
+
for (const author of authors)
|
|
13960
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
13961
|
+
return true;
|
|
13962
|
+
return false;
|
|
13963
|
+
};
|
|
13831
13964
|
var independenceReasons = (row, authors) => {
|
|
13832
13965
|
const reasons = [];
|
|
13833
13966
|
if (!row.actor.session)
|
|
13834
13967
|
reasons.push("no_session");
|
|
13835
|
-
else if (authors
|
|
13968
|
+
else if (authoredBy(authors, row.actor.session))
|
|
13836
13969
|
reasons.push("author_session");
|
|
13837
13970
|
if (row.self === true && !reasons.includes("no_session"))
|
|
13838
13971
|
reasons.push("self");
|
|
@@ -14116,7 +14249,7 @@ var productDecision = (task, ledger) => {
|
|
|
14116
14249
|
};
|
|
14117
14250
|
};
|
|
14118
14251
|
var planRecorded = (task, root) => {
|
|
14119
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(
|
|
14252
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path14.resolve(root, ref.path)));
|
|
14120
14253
|
if (cited)
|
|
14121
14254
|
return {
|
|
14122
14255
|
met: true,
|
|
@@ -14175,15 +14308,15 @@ function writeBlockers(task, root) {
|
|
|
14175
14308
|
}
|
|
14176
14309
|
|
|
14177
14310
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14178
|
-
var digestBytes = (value) =>
|
|
14179
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14180
|
-
var relative = (root, candidate) =>
|
|
14311
|
+
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
14312
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path15.sep}`);
|
|
14313
|
+
var relative = (root, candidate) => path15.relative(root, candidate).split(path15.sep).join("/") || ".";
|
|
14181
14314
|
var canonicalPath = (value) => {
|
|
14182
14315
|
if (typeof value !== "string" || !value)
|
|
14183
14316
|
return null;
|
|
14184
14317
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14185
14318
|
return null;
|
|
14186
|
-
const normalized =
|
|
14319
|
+
const normalized = path15.posix.normalize(value);
|
|
14187
14320
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14188
14321
|
return null;
|
|
14189
14322
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14231,21 +14364,21 @@ var gitPaths = (root) => {
|
|
|
14231
14364
|
git: !/not a git repository/i.test(stderr)
|
|
14232
14365
|
};
|
|
14233
14366
|
}
|
|
14234
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14367
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")) : [];
|
|
14235
14368
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14236
14369
|
if (stagedDeleted.status !== 0) {
|
|
14237
14370
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14238
14371
|
if (!/not a git repository/i.test(stderr))
|
|
14239
14372
|
return { paths, uncertain: true, git: true };
|
|
14240
14373
|
} else if (stagedDeleted.stdout) {
|
|
14241
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14374
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")));
|
|
14242
14375
|
}
|
|
14243
14376
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14244
14377
|
};
|
|
14245
14378
|
var walk = (root, directory, output) => {
|
|
14246
14379
|
let names;
|
|
14247
14380
|
try {
|
|
14248
|
-
names =
|
|
14381
|
+
names = fs13.readdirSync(directory);
|
|
14249
14382
|
} catch {
|
|
14250
14383
|
return true;
|
|
14251
14384
|
}
|
|
@@ -14253,10 +14386,10 @@ var walk = (root, directory, output) => {
|
|
|
14253
14386
|
for (const name of names) {
|
|
14254
14387
|
if (name === ".git" || name === ".workit")
|
|
14255
14388
|
continue;
|
|
14256
|
-
const target =
|
|
14389
|
+
const target = path15.join(directory, name);
|
|
14257
14390
|
let stat;
|
|
14258
14391
|
try {
|
|
14259
|
-
stat =
|
|
14392
|
+
stat = fs13.lstatSync(target);
|
|
14260
14393
|
} catch {
|
|
14261
14394
|
uncertain = true;
|
|
14262
14395
|
continue;
|
|
@@ -14271,14 +14404,14 @@ var walk = (root, directory, output) => {
|
|
|
14271
14404
|
var scopeRoots = (root, scope) => {
|
|
14272
14405
|
const roots = [];
|
|
14273
14406
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14274
|
-
const target =
|
|
14407
|
+
const target = path15.resolve(root, item);
|
|
14275
14408
|
if (!inside(root, target))
|
|
14276
14409
|
return failure2("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` }] });
|
|
14277
14410
|
let ancestor = target;
|
|
14278
|
-
while (!
|
|
14279
|
-
ancestor =
|
|
14411
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
14412
|
+
ancestor = path15.dirname(ancestor);
|
|
14280
14413
|
try {
|
|
14281
|
-
if (!inside(root,
|
|
14414
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
14282
14415
|
return failure2("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` }] });
|
|
14283
14416
|
} catch {
|
|
14284
14417
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -14290,7 +14423,7 @@ var scopeRoots = (root, scope) => {
|
|
|
14290
14423
|
function captureCandidate(root, scope, environment = []) {
|
|
14291
14424
|
let checkout;
|
|
14292
14425
|
try {
|
|
14293
|
-
checkout =
|
|
14426
|
+
checkout = fs13.realpathSync(root);
|
|
14294
14427
|
} catch {
|
|
14295
14428
|
return failure2("invalid_input", "candidate root does not exist");
|
|
14296
14429
|
}
|
|
@@ -14308,11 +14441,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14308
14441
|
if (item === ".git" || item === ".workit")
|
|
14309
14442
|
continue;
|
|
14310
14443
|
names.add(item);
|
|
14311
|
-
if (!
|
|
14444
|
+
if (!fs13.existsSync(target))
|
|
14312
14445
|
continue;
|
|
14313
14446
|
let stat;
|
|
14314
14447
|
try {
|
|
14315
|
-
stat =
|
|
14448
|
+
stat = fs13.lstatSync(target);
|
|
14316
14449
|
} catch {
|
|
14317
14450
|
uncertain = true;
|
|
14318
14451
|
continue;
|
|
@@ -14328,17 +14461,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14328
14461
|
for (const item of [...names].toSorted()) {
|
|
14329
14462
|
if (!scopeMatches(item, normalizedScope))
|
|
14330
14463
|
continue;
|
|
14331
|
-
const target =
|
|
14464
|
+
const target = path15.join(checkout, item);
|
|
14332
14465
|
try {
|
|
14333
|
-
const stat =
|
|
14466
|
+
const stat = fs13.lstatSync(target);
|
|
14334
14467
|
if (stat.isSymbolicLink()) {
|
|
14335
|
-
const link =
|
|
14468
|
+
const link = fs13.readlinkSync(target);
|
|
14336
14469
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
14337
14470
|
} else if (stat.isFile()) {
|
|
14338
14471
|
files.push({
|
|
14339
14472
|
path: item,
|
|
14340
14473
|
kind: "file",
|
|
14341
|
-
digest: digestBytes(
|
|
14474
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
14342
14475
|
executable: (stat.mode & 73) !== 0
|
|
14343
14476
|
});
|
|
14344
14477
|
} else if (!stat.isDirectory()) {
|
|
@@ -14503,7 +14636,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
14503
14636
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
14504
14637
|
let root;
|
|
14505
14638
|
try {
|
|
14506
|
-
root =
|
|
14639
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
14507
14640
|
} catch {
|
|
14508
14641
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
14509
14642
|
}
|
|
@@ -14512,23 +14645,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14512
14645
|
continue;
|
|
14513
14646
|
if (!reference.digest)
|
|
14514
14647
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14515
|
-
const target =
|
|
14516
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
14648
|
+
const target = path15.resolve(root, reference.path);
|
|
14649
|
+
if (target !== root && !target.startsWith(`${root}${path15.sep}`))
|
|
14517
14650
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14518
14651
|
fields: [
|
|
14519
14652
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14520
14653
|
]
|
|
14521
14654
|
});
|
|
14522
14655
|
try {
|
|
14523
|
-
const below =
|
|
14656
|
+
const below = path15.relative(root, target);
|
|
14524
14657
|
let current = root;
|
|
14525
|
-
for (const segment of below.split(
|
|
14658
|
+
for (const segment of below.split(path15.sep)) {
|
|
14526
14659
|
if (!segment)
|
|
14527
14660
|
continue;
|
|
14528
|
-
current =
|
|
14529
|
-
const stat =
|
|
14530
|
-
const real =
|
|
14531
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
14661
|
+
current = path15.join(current, segment);
|
|
14662
|
+
const stat = fs13.lstatSync(current);
|
|
14663
|
+
const real = fs13.realpathSync(current);
|
|
14664
|
+
if (real !== root && !real.startsWith(`${root}${path15.sep}`))
|
|
14532
14665
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14533
14666
|
fields: [
|
|
14534
14667
|
{
|
|
@@ -14544,7 +14677,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14544
14677
|
if (current !== target && !stat.isDirectory())
|
|
14545
14678
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
14546
14679
|
}
|
|
14547
|
-
if (digestBytes(
|
|
14680
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
14548
14681
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
14549
14682
|
} catch {
|
|
14550
14683
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15401,7 +15534,7 @@ var judgeInputSummary = (raw) => {
|
|
|
15401
15534
|
|
|
15402
15535
|
// packages/workit-core/src/autonomy.ts
|
|
15403
15536
|
import os3 from "node:os";
|
|
15404
|
-
import
|
|
15537
|
+
import path16 from "node:path";
|
|
15405
15538
|
var accountHome = () => {
|
|
15406
15539
|
try {
|
|
15407
15540
|
return os3.userInfo().homedir;
|
|
@@ -15410,10 +15543,10 @@ var accountHome = () => {
|
|
|
15410
15543
|
}
|
|
15411
15544
|
};
|
|
15412
15545
|
var grantsHome = { resolve: accountHome };
|
|
15413
|
-
var grantsDir = () =>
|
|
15546
|
+
var grantsDir = () => path16.join(grantsHome.resolve(), ".config", "workit");
|
|
15414
15547
|
var grantsOverride = () => {
|
|
15415
|
-
const configured =
|
|
15416
|
-
return configured ===
|
|
15548
|
+
const configured = path16.resolve(resolveConfigDir());
|
|
15549
|
+
return configured === path16.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path16.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
15417
15550
|
};
|
|
15418
15551
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
15419
15552
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -15599,38 +15732,45 @@ var compactReference = (ref) => {
|
|
|
15599
15732
|
};
|
|
15600
15733
|
return { kind: ref.kind };
|
|
15601
15734
|
};
|
|
15602
|
-
|
|
15603
|
-
const
|
|
15604
|
-
|
|
15605
|
-
|
|
15606
|
-
|
|
15607
|
-
|
|
15608
|
-
|
|
15609
|
-
|
|
15610
|
-
|
|
15611
|
-
|
|
15612
|
-
|
|
15613
|
-
|
|
15614
|
-
|
|
15615
|
-
|
|
15616
|
-
|
|
15617
|
-
|
|
15735
|
+
var newestDecisionFirst = (left, right) => {
|
|
15736
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
15737
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
15738
|
+
if (leftAt !== rightAt)
|
|
15739
|
+
return leftAt > rightAt ? -1 : 1;
|
|
15740
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
15741
|
+
};
|
|
15742
|
+
var compactDecisions = (sources) => sources.flatMap((source) => source ? source.decisions.toSorted(newestDecisionFirst).map((entry) => ({
|
|
15743
|
+
id: entry.id,
|
|
15744
|
+
purpose: entry.data.purpose,
|
|
15745
|
+
status: entry.data.response,
|
|
15746
|
+
digest: entry.data.digest,
|
|
15747
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${source.taskId} view=full; decision=${entry.id}`,
|
|
15748
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference),
|
|
15749
|
+
origin: source.origin ?? undefined
|
|
15750
|
+
})) : []).slice(0, COMPACT_MAX_ITEMS);
|
|
15751
|
+
function compactTaskContext(view, inherited = null) {
|
|
15752
|
+
const decisions = compactDecisions([
|
|
15753
|
+
view && { origin: null, taskId: view.task.id, decisions: view.task.decisions },
|
|
15754
|
+
inherited
|
|
15755
|
+
]);
|
|
15756
|
+
const methods = view?.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
15618
15757
|
id: method.id,
|
|
15619
15758
|
assurance: method.assurance,
|
|
15620
15759
|
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
15621
15760
|
})) : [];
|
|
15761
|
+
const stale = view?.evidence.filter((entry) => entry.status === "stale").length;
|
|
15622
15762
|
const gaps = Array.from(new Set([
|
|
15623
|
-
...
|
|
15624
|
-
...view
|
|
15625
|
-
...view
|
|
15763
|
+
...stale ? [`stale evidence: ${stale}; workit task inspect --task ${view.task.id} --view full`] : [],
|
|
15764
|
+
...(view?.requirements ?? []).filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
15765
|
+
...(view?.task.progress.blockers ?? []).map((entry) => entry.reason)
|
|
15626
15766
|
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
15627
15767
|
const context = {
|
|
15628
|
-
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
15629
|
-
status: view
|
|
15768
|
+
objective: view ? compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "" : "no task on this branch yet",
|
|
15769
|
+
status: view?.task.status ?? null,
|
|
15630
15770
|
decisions,
|
|
15631
15771
|
methods,
|
|
15632
15772
|
gaps,
|
|
15633
|
-
nextAction: compactText(view
|
|
15773
|
+
nextAction: compactText(view?.task.progress.nextAction ?? null, COMPACT_TEXT_BYTES)
|
|
15634
15774
|
};
|
|
15635
15775
|
let encoded = JSON.stringify(context);
|
|
15636
15776
|
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
@@ -15819,6 +15959,13 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
15819
15959
|
"worker.cancel",
|
|
15820
15960
|
"state.export"
|
|
15821
15961
|
]);
|
|
15962
|
+
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
15963
|
+
var workerCommand = (taskId, request) => {
|
|
15964
|
+
const { action, workerId, reason, report } = request;
|
|
15965
|
+
const rest = JSON.stringify({ reason, report });
|
|
15966
|
+
const payload = rest === "{}" ? "" : ` --payload '${rest.replace(/'/gu, "'\\''")}'`;
|
|
15967
|
+
return `workit worker ${String(action)} ${String(workerId)} --task ${taskId}${payload}`;
|
|
15968
|
+
};
|
|
15822
15969
|
var CREATING_ACTIONS = new Set([
|
|
15823
15970
|
"task.revise",
|
|
15824
15971
|
"task.progress",
|
|
@@ -16069,6 +16216,17 @@ class WorkitCore {
|
|
|
16069
16216
|
const root = this.contextRootError();
|
|
16070
16217
|
if (!root.ok)
|
|
16071
16218
|
return root;
|
|
16219
|
+
const addressed = WORKER_ADDRESSED_ACTIONS.has(action);
|
|
16220
|
+
const workerId = addressed ? value.workerId : this.context.workerId ?? null;
|
|
16221
|
+
if (workerId !== null) {
|
|
16222
|
+
const owner = this.store.taskForWorker(workerId);
|
|
16223
|
+
if (!owner.ok)
|
|
16224
|
+
return owner;
|
|
16225
|
+
if (owner.data)
|
|
16226
|
+
return success(null, null, { ...request, taskId: owner.data });
|
|
16227
|
+
if (addressed)
|
|
16228
|
+
return failure2("not_found", `worker ${workerId} is on no open task; find its task with \`workit task status --all\`, then run \`${workerCommand("<task-id>", request)}\``);
|
|
16229
|
+
}
|
|
16072
16230
|
const create = CREATING_ACTIONS.has(action) && (this.context.workerId ?? null) === null;
|
|
16073
16231
|
const found = this.store.implicitTask({
|
|
16074
16232
|
provenance: provenance(this.context),
|
|
@@ -16775,7 +16933,9 @@ class WorkitCore {
|
|
|
16775
16933
|
if (!resolved.ok)
|
|
16776
16934
|
return resolved;
|
|
16777
16935
|
const value = resolved.data;
|
|
16778
|
-
|
|
16936
|
+
const result = this.retryOmittedRevisions(value, () => this.workerOnce(value));
|
|
16937
|
+
const { action, taskId } = value ?? {};
|
|
16938
|
+
return result.ok || request?.taskId !== undefined || typeof taskId !== "string" || !WORKER_ADDRESSED_ACTIONS.has(`worker.${String(action)}`) ? result : { ...result, error: `${result.error}; run \`${workerCommand(taskId, value)}\`` };
|
|
16779
16939
|
}
|
|
16780
16940
|
workerOnce(request) {
|
|
16781
16941
|
const root = this.contextRootError();
|
|
@@ -16980,10 +17140,18 @@ class WorkitCore {
|
|
|
16980
17140
|
requirements
|
|
16981
17141
|
});
|
|
16982
17142
|
}
|
|
16983
|
-
compactContext(taskId, freshness) {
|
|
17143
|
+
compactContext(taskId, freshness, origin = null) {
|
|
16984
17144
|
const root = this.contextRootError();
|
|
16985
17145
|
if (!root.ok)
|
|
16986
17146
|
return root;
|
|
17147
|
+
const base = origin && this.store.readStoreTask(origin.taskId);
|
|
17148
|
+
const inherited = base?.ok && base.data ? {
|
|
17149
|
+
origin: origin.origin,
|
|
17150
|
+
taskId: base.data.id,
|
|
17151
|
+
decisions: base.data.decisions.filter((entry) => entry.data.revoked === null)
|
|
17152
|
+
} : null;
|
|
17153
|
+
if (taskId === null)
|
|
17154
|
+
return success(null, null, inherited?.decisions.length ? compactTaskContext(null, inherited) : "");
|
|
16987
17155
|
const task = this.store.readTask(taskId);
|
|
16988
17156
|
if (!task.ok)
|
|
16989
17157
|
return task;
|
|
@@ -16993,7 +17161,7 @@ class WorkitCore {
|
|
|
16993
17161
|
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
16994
17162
|
if (!view.ok)
|
|
16995
17163
|
return view;
|
|
16996
|
-
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
17164
|
+
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data, inherited));
|
|
16997
17165
|
}
|
|
16998
17166
|
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
16999
17167
|
const workspace = this.store.readWorkspace();
|
|
@@ -17288,8 +17456,9 @@ var currentTaskEntry = (entries, session, selection) => {
|
|
|
17288
17456
|
const active = entries.filter((entry) => entry.status === "active");
|
|
17289
17457
|
return active.length === 1 ? active[0] : null;
|
|
17290
17458
|
};
|
|
17459
|
+
var otherConversation = (entry, session) => entry.sessions.some((item) => item.host !== "workit_cli" && !(item.host === session.host && item.handle === session.handle));
|
|
17291
17460
|
var implicitTaskEntry = (store, entries, session) => {
|
|
17292
|
-
const open = entries.filter((entry) => entry.key !== null && entry.status !== "closed" && !entry
|
|
17461
|
+
const open = entries.filter((entry) => entry.key !== null && entry.status !== "closed" && !otherConversation(entry, session));
|
|
17293
17462
|
if (open.length === 0)
|
|
17294
17463
|
return null;
|
|
17295
17464
|
const key = store.currentKey();
|
|
@@ -17330,18 +17499,23 @@ function sessionCompactContext(store, session, context, selection = "session-bou
|
|
|
17330
17499
|
if (!listed.ok)
|
|
17331
17500
|
return migrationPending(listed) ? MIGRATION_PENDING_NOTE : null;
|
|
17332
17501
|
const entry = currentTaskEntry(listed.data, session, selection) ?? implicitTaskEntry(store, listed.data, session);
|
|
17333
|
-
return
|
|
17502
|
+
return entryCompactContext(store, entry, session, context);
|
|
17334
17503
|
}
|
|
17335
17504
|
function entryCompactContext(store, entry, session, context) {
|
|
17505
|
+
const found = store.originTaskEntry();
|
|
17506
|
+
const origin = found.ok && found.data && found.data.entry.id !== entry?.id && !otherConversation(found.data.entry, session) ? found.data : null;
|
|
17507
|
+
if (!entry && !origin)
|
|
17508
|
+
return null;
|
|
17336
17509
|
const workspace = store.readWorkspace();
|
|
17337
|
-
if (!workspace.ok || !workspace.data)
|
|
17510
|
+
if (!workspace.ok || entry && !workspace.data)
|
|
17338
17511
|
return null;
|
|
17339
17512
|
const slot = `${store.root}\x00${session.host}\x00${session.handle}`;
|
|
17340
17513
|
const key = canonicalJson({
|
|
17341
|
-
task: entry
|
|
17342
|
-
revision: entry
|
|
17343
|
-
file: entry
|
|
17344
|
-
|
|
17514
|
+
task: entry?.id ?? null,
|
|
17515
|
+
revision: entry?.revision ?? null,
|
|
17516
|
+
file: entry?.file ?? null,
|
|
17517
|
+
origin: origin && origin.entry.file,
|
|
17518
|
+
workspace: workspace.data?.revision ?? null,
|
|
17345
17519
|
caller: context.caller,
|
|
17346
17520
|
workerId: context.workerId ?? null,
|
|
17347
17521
|
capabilities: context.capabilities
|
|
@@ -17349,15 +17523,15 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17349
17523
|
const hit = cache.get(slot);
|
|
17350
17524
|
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
17351
17525
|
return hit.value;
|
|
17352
|
-
const task = store.readTask(entry.id);
|
|
17353
|
-
if (!task.ok)
|
|
17526
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
17527
|
+
if (task && !task.ok)
|
|
17354
17528
|
return null;
|
|
17355
17529
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17356
|
-
const observed = task
|
|
17357
|
-
const files = task
|
|
17530
|
+
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
17531
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
|
|
17358
17532
|
const observedFiles = filesKey(files);
|
|
17359
|
-
const compact = new WorkitCore(store, context).compactContext(entry
|
|
17360
|
-
if (!compact.ok)
|
|
17533
|
+
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
17534
|
+
if (!compact.ok || !compact.data)
|
|
17361
17535
|
return null;
|
|
17362
17536
|
cache.delete(slot);
|
|
17363
17537
|
cache.set(slot, {
|
|
@@ -17396,7 +17570,7 @@ ${options.addendum}` : ""}
|
|
|
17396
17570
|
if (!listed.ok)
|
|
17397
17571
|
throw new Error(listed.error);
|
|
17398
17572
|
const entry = currentTaskEntry(listed.data, session, descriptor.context.task) ?? implicitTaskEntry(store, listed.data, session);
|
|
17399
|
-
const text =
|
|
17573
|
+
const text = entryCompactContext(store, entry, session, hookOperationContext(input, descriptor));
|
|
17400
17574
|
if (text)
|
|
17401
17575
|
compact = `
|
|
17402
17576
|
<workit-task-context>${text}</workit-task-context>`;
|
|
@@ -17423,7 +17597,7 @@ var turnContextText = (input, descriptor) => {
|
|
|
17423
17597
|
}
|
|
17424
17598
|
};
|
|
17425
17599
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
17426
|
-
var digestOf = (text) =>
|
|
17600
|
+
var digestOf = (text) => createHash11("sha256").update(text).digest("hex");
|
|
17427
17601
|
var turnMarker = (input) => {
|
|
17428
17602
|
if (!input.session.id)
|
|
17429
17603
|
return null;
|
|
@@ -17432,32 +17606,32 @@ var turnMarker = (input) => {
|
|
|
17432
17606
|
if (location instanceof Error)
|
|
17433
17607
|
return null;
|
|
17434
17608
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
17435
|
-
return
|
|
17609
|
+
return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
17436
17610
|
} catch {
|
|
17437
17611
|
return null;
|
|
17438
17612
|
}
|
|
17439
17613
|
};
|
|
17440
17614
|
var writeTurnDigest = (file, digest) => {
|
|
17441
17615
|
try {
|
|
17442
|
-
|
|
17443
|
-
|
|
17616
|
+
fs14.mkdirSync(path17.dirname(file), { recursive: true });
|
|
17617
|
+
fs14.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
17444
17618
|
`);
|
|
17445
17619
|
} catch {}
|
|
17446
17620
|
};
|
|
17447
17621
|
var clearTurnDigest = (file) => {
|
|
17448
17622
|
try {
|
|
17449
|
-
|
|
17623
|
+
fs14.rmSync(file, { force: true });
|
|
17450
17624
|
} catch {}
|
|
17451
17625
|
};
|
|
17452
17626
|
var pruneTurnDigests = (dir, now) => {
|
|
17453
17627
|
try {
|
|
17454
|
-
for (const name of
|
|
17628
|
+
for (const name of fs14.readdirSync(dir)) {
|
|
17455
17629
|
if (!name.startsWith("turn-"))
|
|
17456
17630
|
continue;
|
|
17457
|
-
const file =
|
|
17631
|
+
const file = path17.join(dir, name);
|
|
17458
17632
|
try {
|
|
17459
|
-
if (now -
|
|
17460
|
-
|
|
17633
|
+
if (now - fs14.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
17634
|
+
fs14.rmSync(file, { force: true });
|
|
17461
17635
|
} catch {}
|
|
17462
17636
|
}
|
|
17463
17637
|
} catch {}
|
|
@@ -17473,7 +17647,7 @@ var changedTurnContext = (input, text) => {
|
|
|
17473
17647
|
return text;
|
|
17474
17648
|
const digest = digestOf(text);
|
|
17475
17649
|
try {
|
|
17476
|
-
const previous = JSON.parse(
|
|
17650
|
+
const previous = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
17477
17651
|
if (previous.digest === digest)
|
|
17478
17652
|
return null;
|
|
17479
17653
|
} catch {}
|
|
@@ -17484,14 +17658,14 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
17484
17658
|
const file = turnMarker(input);
|
|
17485
17659
|
if (!file)
|
|
17486
17660
|
return;
|
|
17487
|
-
pruneTurnDigests(
|
|
17661
|
+
pruneTurnDigests(path17.dirname(file), Date.now());
|
|
17488
17662
|
if (text)
|
|
17489
17663
|
writeTurnDigest(file, digestOf(text));
|
|
17490
17664
|
else
|
|
17491
17665
|
clearTurnDigest(file);
|
|
17492
17666
|
};
|
|
17493
17667
|
// packages/workit-core/src/core/branch.ts
|
|
17494
|
-
import { createHash as
|
|
17668
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
17495
17669
|
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
17496
17670
|
const read = workspaceReleaseTracks(workspaceRoot);
|
|
17497
17671
|
if (read.error)
|
|
@@ -17559,7 +17733,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
|
17559
17733
|
const source = resolved.provenance.branchPolicy;
|
|
17560
17734
|
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
17561
17735
|
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
17562
|
-
const fingerprint =
|
|
17736
|
+
const fingerprint = createHash12("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
17563
17737
|
return {
|
|
17564
17738
|
branchPolicy,
|
|
17565
17739
|
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
@@ -17608,7 +17782,7 @@ var shellPolicy = (cwd, command) => {
|
|
|
17608
17782
|
};
|
|
17609
17783
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
17610
17784
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
17611
|
-
import
|
|
17785
|
+
import path18 from "node:path";
|
|
17612
17786
|
|
|
17613
17787
|
// packages/workit-core/src/hooks/shell-words.ts
|
|
17614
17788
|
var closingQuote = (command, start, dialect) => {
|
|
@@ -17738,22 +17912,22 @@ var NONE2 = { kind: "none" };
|
|
|
17738
17912
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
17739
17913
|
var canonical2 = (file) => {
|
|
17740
17914
|
let rest = "";
|
|
17741
|
-
for (let dir =
|
|
17915
|
+
for (let dir = path18.resolve(file);; dir = path18.dirname(dir)) {
|
|
17742
17916
|
try {
|
|
17743
|
-
return
|
|
17917
|
+
return path18.join(realpathSync5(dir), rest);
|
|
17744
17918
|
} catch {
|
|
17745
|
-
if (
|
|
17746
|
-
return
|
|
17747
|
-
rest = rest ?
|
|
17919
|
+
if (path18.dirname(dir) === dir)
|
|
17920
|
+
return path18.resolve(file);
|
|
17921
|
+
rest = rest ? path18.join(path18.basename(dir), rest) : path18.basename(dir);
|
|
17748
17922
|
}
|
|
17749
17923
|
}
|
|
17750
17924
|
};
|
|
17751
17925
|
var exempt = (cwd, root, file, plans) => {
|
|
17752
17926
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
17753
|
-
const relative =
|
|
17754
|
-
if (relative.startsWith("..") ||
|
|
17927
|
+
const relative = path18.relative(fold(canonical2(root)), fold(canonical2(path18.resolve(cwd, file))));
|
|
17928
|
+
if (relative.startsWith("..") || path18.isAbsolute(relative))
|
|
17755
17929
|
return true;
|
|
17756
|
-
const posix = relative.split(
|
|
17930
|
+
const posix = relative.split(path18.sep).join("/");
|
|
17757
17931
|
const segments = posix.split("/");
|
|
17758
17932
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
17759
17933
|
};
|
|
@@ -17851,7 +18025,7 @@ var shellWrites = (command) => {
|
|
|
17851
18025
|
return { writes: true, targets: unknown ? null : targets };
|
|
17852
18026
|
};
|
|
17853
18027
|
var cache2 = new Map;
|
|
17854
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
18028
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path18.resolve(root, file)) ?? "-").join("\x00");
|
|
17855
18029
|
var gateFor = (cwd) => {
|
|
17856
18030
|
const store = new TaskStore(cwd);
|
|
17857
18031
|
const listed = store.listTaskIndex();
|
|
@@ -17911,15 +18085,15 @@ import { spawnSync as spawnSync12 } from "node:child_process";
|
|
|
17911
18085
|
|
|
17912
18086
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
17913
18087
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17914
|
-
import { createHash as
|
|
17915
|
-
import
|
|
18088
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
18089
|
+
import fs16 from "node:fs";
|
|
17916
18090
|
import os4 from "node:os";
|
|
17917
|
-
import
|
|
18091
|
+
import path20 from "node:path";
|
|
17918
18092
|
|
|
17919
18093
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
17920
|
-
import { createHash as
|
|
17921
|
-
import
|
|
17922
|
-
import
|
|
18094
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
18095
|
+
import fs15 from "node:fs";
|
|
18096
|
+
import path19 from "node:path";
|
|
17923
18097
|
var sessionMarkerFile = (input, session, store) => {
|
|
17924
18098
|
let dir = store;
|
|
17925
18099
|
if (dir === undefined) {
|
|
@@ -17928,13 +18102,13 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
17928
18102
|
return null;
|
|
17929
18103
|
dir = location.dir;
|
|
17930
18104
|
}
|
|
17931
|
-
const name =
|
|
17932
|
-
return
|
|
18105
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18106
|
+
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
17933
18107
|
};
|
|
17934
18108
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17935
18109
|
var readSessionMarker = (file) => {
|
|
17936
18110
|
try {
|
|
17937
|
-
const value = JSON.parse(
|
|
18111
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17938
18112
|
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17939
18113
|
} catch {
|
|
17940
18114
|
return { nudged: [], loaded: [], raw: [] };
|
|
@@ -17942,8 +18116,8 @@ var readSessionMarker = (file) => {
|
|
|
17942
18116
|
};
|
|
17943
18117
|
var writeSessionMarker = (file, marker) => {
|
|
17944
18118
|
try {
|
|
17945
|
-
|
|
17946
|
-
|
|
18119
|
+
fs15.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18120
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
17947
18121
|
} catch {}
|
|
17948
18122
|
};
|
|
17949
18123
|
|
|
@@ -17964,12 +18138,12 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
17964
18138
|
"--config-env",
|
|
17965
18139
|
"--super-prefix"
|
|
17966
18140
|
]);
|
|
17967
|
-
var isAbsolute = (value) =>
|
|
18141
|
+
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17968
18142
|
var joinDir = (base, next) => {
|
|
17969
18143
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17970
18144
|
return;
|
|
17971
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17972
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18145
|
+
const expanded = next === "~" || next.startsWith("~/") ? path20.join(os4.homedir(), next.slice(1)) : next;
|
|
18146
|
+
return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
|
|
17973
18147
|
};
|
|
17974
18148
|
var cdTarget = (words) => {
|
|
17975
18149
|
const args = words.slice(1);
|
|
@@ -18222,7 +18396,7 @@ var repoAt = (dir) => {
|
|
|
18222
18396
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
18223
18397
|
};
|
|
18224
18398
|
var isWorkitWorkspace = (repo) => {
|
|
18225
|
-
if (
|
|
18399
|
+
if (fs16.existsSync(repo.location.dir))
|
|
18226
18400
|
return true;
|
|
18227
18401
|
try {
|
|
18228
18402
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -18253,17 +18427,17 @@ var headSha2 = (repo) => {
|
|
|
18253
18427
|
if (!git || git.reftable)
|
|
18254
18428
|
return null;
|
|
18255
18429
|
try {
|
|
18256
|
-
const head =
|
|
18430
|
+
const head = fs16.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18257
18431
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18258
18432
|
return head;
|
|
18259
18433
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18260
18434
|
if (!ref)
|
|
18261
18435
|
return null;
|
|
18262
|
-
const common =
|
|
18436
|
+
const common = path20.dirname(repo.location.dir);
|
|
18263
18437
|
try {
|
|
18264
|
-
return
|
|
18438
|
+
return fs16.readFileSync(path20.join(common, ref), "utf8").trim() || null;
|
|
18265
18439
|
} catch {}
|
|
18266
|
-
const packed =
|
|
18440
|
+
const packed = fs16.readFileSync(path20.join(common, "packed-refs"), "utf8");
|
|
18267
18441
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18268
18442
|
} catch {
|
|
18269
18443
|
return null;
|
|
@@ -18300,7 +18474,7 @@ var actionsIn = (cwd, command, dialect) => {
|
|
|
18300
18474
|
const action = rawAction(invocation);
|
|
18301
18475
|
if (!action)
|
|
18302
18476
|
continue;
|
|
18303
|
-
const dir =
|
|
18477
|
+
const dir = path20.resolve(cwd, invocation.dir ?? ".");
|
|
18304
18478
|
if (!repos.has(dir)) {
|
|
18305
18479
|
const repo = repoAt(dir);
|
|
18306
18480
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18369,8 +18543,8 @@ function rawGitPre(input, command, options = {}) {
|
|
|
18369
18543
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
18370
18544
|
var NEXT_COMMAND = "next";
|
|
18371
18545
|
var MARKER_TTL_SECONDS = 3600;
|
|
18372
|
-
var markerFile = (repo, session, key) =>
|
|
18373
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
18546
|
+
var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18547
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
18374
18548
|
function noteRawCommit(input, command, key) {
|
|
18375
18549
|
try {
|
|
18376
18550
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -18380,8 +18554,8 @@ function noteRawCommit(input, command, key) {
|
|
|
18380
18554
|
if (action.kind !== "commit")
|
|
18381
18555
|
continue;
|
|
18382
18556
|
const file = markerFile(repo, input.session.id, key);
|
|
18383
|
-
|
|
18384
|
-
|
|
18557
|
+
fs16.mkdirSync(path20.dirname(file), { recursive: true });
|
|
18558
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18385
18559
|
`);
|
|
18386
18560
|
}
|
|
18387
18561
|
} catch {}
|
|
@@ -18404,11 +18578,11 @@ var headCommit = (dir) => {
|
|
|
18404
18578
|
var settle2 = (input, repo, file) => {
|
|
18405
18579
|
let marker;
|
|
18406
18580
|
try {
|
|
18407
|
-
marker = JSON.parse(
|
|
18581
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
18408
18582
|
} catch {
|
|
18409
18583
|
return;
|
|
18410
18584
|
}
|
|
18411
|
-
|
|
18585
|
+
fs16.rmSync(file, { force: true });
|
|
18412
18586
|
const session = input.session.id;
|
|
18413
18587
|
const now = Date.now() / 1000;
|
|
18414
18588
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18457,7 +18631,7 @@ function settlePendingCommit(input) {
|
|
|
18457
18631
|
if (!repo)
|
|
18458
18632
|
return;
|
|
18459
18633
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
18460
|
-
if (
|
|
18634
|
+
if (fs16.existsSync(file))
|
|
18461
18635
|
settle2(input, repo, file);
|
|
18462
18636
|
} catch {}
|
|
18463
18637
|
}
|
|
@@ -18847,12 +19021,12 @@ ${line}` };
|
|
|
18847
19021
|
};
|
|
18848
19022
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18849
19023
|
import { existsSync as existsSync6, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
18850
|
-
import
|
|
19024
|
+
import path21 from "node:path";
|
|
18851
19025
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18852
19026
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
18853
19027
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
18854
19028
|
var existingDirectory = (value) => {
|
|
18855
|
-
if (!nonEmpty2(value) || !
|
|
19029
|
+
if (!nonEmpty2(value) || !path21.isAbsolute(value) || !existsSync6(value))
|
|
18856
19030
|
return null;
|
|
18857
19031
|
try {
|
|
18858
19032
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -18952,6 +19126,8 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
18952
19126
|
if (role !== "judge" || !event.agentId)
|
|
18953
19127
|
return readOnly;
|
|
18954
19128
|
const own = subagentSession(session, event.agentId);
|
|
19129
|
+
if (session)
|
|
19130
|
+
recordIssuedSession(input.cwd || process.cwd(), session, own);
|
|
18955
19131
|
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> --session ${own} ...\` so the ledger tells it apart from the author.`;
|
|
18956
19132
|
};
|
|
18957
19133
|
function handleHook(input, deps) {
|
|
@@ -19583,12 +19759,12 @@ var codexAdapter = {
|
|
|
19583
19759
|
};
|
|
19584
19760
|
},
|
|
19585
19761
|
render: render2,
|
|
19586
|
-
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other
|
|
19762
|
+
addendum: (input) => `<workit-codex-mutations>Codex MCP is read-only: unattested callers cannot mutate. Run workit verbs with the workit CLI on the shell (node_modules/.bin/workit, or npx -y @brainervirus/workit-cli): check, git branch|commit|push, pr, ci, stack, ledger, handoff; task-family mutations take --json${input.session.id ? ` --actor ${input.session.id}` : ""}. workit acts as this thread (CODEX_THREAD_ID${input.session.id ? `=${input.session.id}` : ""}) unless WORKIT_SESSION_ID is set. A verifier or reviewer records workit ledger verdict under its own session, never the author's: a workit-verifier or workit-reviewer agent uses the --session its SubagentStart context names; any other passes --as verifier (an id derived from its own thread). A hand-minted id such as <lead>-v1 is refused as forged_session. Merge and release need a workspace grant (workit grant show); a question answer is not host permission.</workit-codex-mutations>`
|
|
19587
19763
|
};
|
|
19588
19764
|
|
|
19589
19765
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19590
|
-
import
|
|
19591
|
-
import
|
|
19766
|
+
import fs17 from "node:fs";
|
|
19767
|
+
import path22 from "node:path";
|
|
19592
19768
|
var undocumented = { support: "undocumented", native: null };
|
|
19593
19769
|
var none = { support: "none", native: null };
|
|
19594
19770
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -19785,17 +19961,17 @@ var protocolEvent2 = (input) => {
|
|
|
19785
19961
|
};
|
|
19786
19962
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19787
19963
|
var lastAssistantText = (file) => {
|
|
19788
|
-
if (!file || !
|
|
19964
|
+
if (!file || !path22.isAbsolute(file))
|
|
19789
19965
|
return null;
|
|
19790
19966
|
try {
|
|
19791
|
-
const size =
|
|
19967
|
+
const size = fs17.statSync(file).size;
|
|
19792
19968
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
19793
19969
|
const buffer = Buffer.alloc(size - start);
|
|
19794
|
-
const fd =
|
|
19970
|
+
const fd = fs17.openSync(file, "r");
|
|
19795
19971
|
try {
|
|
19796
|
-
|
|
19972
|
+
fs17.readSync(fd, buffer, 0, buffer.length, start);
|
|
19797
19973
|
} finally {
|
|
19798
|
-
|
|
19974
|
+
fs17.closeSync(fd);
|
|
19799
19975
|
}
|
|
19800
19976
|
const lines = buffer.toString("utf8").split(`
|
|
19801
19977
|
`);
|
|
@@ -19822,7 +19998,7 @@ var lastAssistantText = (file) => {
|
|
|
19822
19998
|
return null;
|
|
19823
19999
|
}
|
|
19824
20000
|
};
|
|
19825
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
20001
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path22.sep) ? root : `${root}${path22.sep}`);
|
|
19826
20002
|
var hookCwd = (input) => {
|
|
19827
20003
|
const cwd = existingDirectory(input.cwd);
|
|
19828
20004
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|