@brainervirus/workit-codex 8.9.1 → 8.11.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 +151 -134
- package/dist/{workit-hook-vjkepg0b.js → workit-hook-gtdf2ra8.js} +975 -640
- 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";
|
|
@@ -10466,14 +10498,14 @@ var isNewerVersion = (candidate, current) => {
|
|
|
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);
|
|
10467
10499
|
var readText = (file) => {
|
|
10468
10500
|
try {
|
|
10469
|
-
return
|
|
10501
|
+
return fs10.readFileSync(file, "utf8");
|
|
10470
10502
|
} catch {
|
|
10471
10503
|
return "";
|
|
10472
10504
|
}
|
|
10473
10505
|
};
|
|
10474
10506
|
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
10475
10507
|
var branchStart = (location, branch, aliases) => {
|
|
10476
|
-
const first = readText(
|
|
10508
|
+
const first = readText(path9.join(path9.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
10477
10509
|
`, 1)[0];
|
|
10478
10510
|
const created = REFLOG_LINE.exec(first);
|
|
10479
10511
|
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
@@ -10483,7 +10515,7 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10483
10515
|
return start;
|
|
10484
10516
|
if (!location.git)
|
|
10485
10517
|
return null;
|
|
10486
|
-
for (const line of readText(
|
|
10518
|
+
for (const line of readText(path9.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
10487
10519
|
`)) {
|
|
10488
10520
|
const entry = REFLOG_LINE.exec(line);
|
|
10489
10521
|
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
@@ -10493,23 +10525,23 @@ var branchStart = (location, branch, aliases) => {
|
|
|
10493
10525
|
}
|
|
10494
10526
|
return null;
|
|
10495
10527
|
};
|
|
10496
|
-
var refExists = (common, ref) =>
|
|
10528
|
+
var refExists = (common, ref) => fs10.existsSync(path9.join(common, ref)) || readText(path9.join(common, "packed-refs")).includes(` ${ref}
|
|
10497
10529
|
`);
|
|
10498
10530
|
var origins = new Map;
|
|
10499
10531
|
var sameDirectoryIdentity = (left, right) => {
|
|
10500
|
-
if (!
|
|
10532
|
+
if (!path9.isAbsolute(left) || !path9.isAbsolute(right))
|
|
10501
10533
|
return false;
|
|
10502
|
-
const normalizedLeft =
|
|
10503
|
-
const normalizedRight =
|
|
10534
|
+
const normalizedLeft = path9.resolve(left);
|
|
10535
|
+
const normalizedRight = path9.resolve(right);
|
|
10504
10536
|
try {
|
|
10505
|
-
const a =
|
|
10506
|
-
const b =
|
|
10537
|
+
const a = fs10.statSync(normalizedLeft, { bigint: true });
|
|
10538
|
+
const b = fs10.statSync(normalizedRight, { bigint: true });
|
|
10507
10539
|
if (!a.isDirectory() || !b.isDirectory())
|
|
10508
10540
|
return false;
|
|
10509
10541
|
if (process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight)
|
|
10510
10542
|
return true;
|
|
10511
|
-
const canonicalLeft =
|
|
10512
|
-
const canonicalRight =
|
|
10543
|
+
const canonicalLeft = fs10.realpathSync(normalizedLeft);
|
|
10544
|
+
const canonicalRight = fs10.realpathSync(normalizedRight);
|
|
10513
10545
|
if (process.platform === "win32" ? canonicalLeft.toLowerCase() === canonicalRight.toLowerCase() : canonicalLeft === canonicalRight)
|
|
10514
10546
|
return true;
|
|
10515
10547
|
return a.dev === b.dev && a.ino !== 0n && a.ino === b.ino;
|
|
@@ -10546,8 +10578,8 @@ class TaskStore {
|
|
|
10546
10578
|
migrationChecked = false;
|
|
10547
10579
|
compactDue = new Set;
|
|
10548
10580
|
constructor(root, options = {}) {
|
|
10549
|
-
const resolved =
|
|
10550
|
-
this.origin =
|
|
10581
|
+
const resolved = path9.resolve(root);
|
|
10582
|
+
this.origin = fs10.existsSync(resolved) ? fs10.realpathSync(resolved) : resolved;
|
|
10551
10583
|
this.located = resolveStore(this.origin);
|
|
10552
10584
|
this.root = checkoutRootOf(this.origin, this.located);
|
|
10553
10585
|
this.lockTimeoutMs = options.lockTimeoutMs ?? defaultLockTimeout();
|
|
@@ -10555,7 +10587,7 @@ class TaskStore {
|
|
|
10555
10587
|
this.migrateOnRead = options.migrateOnRead;
|
|
10556
10588
|
}
|
|
10557
10589
|
location() {
|
|
10558
|
-
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"))) {
|
|
10559
10591
|
this.located = undefined;
|
|
10560
10592
|
this.migrationChecked = false;
|
|
10561
10593
|
}
|
|
@@ -10619,7 +10651,7 @@ class TaskStore {
|
|
|
10619
10651
|
const tasksDir = this.paths().tasks;
|
|
10620
10652
|
let names;
|
|
10621
10653
|
try {
|
|
10622
|
-
names =
|
|
10654
|
+
names = fs10.existsSync(tasksDir) ? fs10.readdirSync(tasksDir).filter((name) => validId(name)) : [];
|
|
10623
10655
|
} catch (error) {
|
|
10624
10656
|
return failure2("storage_error", `unable to list tasks: ${String(error)}`, {
|
|
10625
10657
|
path: tasksDir
|
|
@@ -10660,7 +10692,7 @@ class TaskStore {
|
|
|
10660
10692
|
const file = this.paths().workspace;
|
|
10661
10693
|
let bytes;
|
|
10662
10694
|
try {
|
|
10663
|
-
bytes = retryTransient(() =>
|
|
10695
|
+
bytes = retryTransient(() => fs10.readFileSync(file, "utf8"));
|
|
10664
10696
|
} catch (error) {
|
|
10665
10697
|
if (error.code === "ENOENT")
|
|
10666
10698
|
return success(null, null, null);
|
|
@@ -10711,7 +10743,7 @@ class TaskStore {
|
|
|
10711
10743
|
const moved = this.bindLocked(again.data.id, key, input.now);
|
|
10712
10744
|
return moved.ok ? success(moved.data.revision, null, { task: moved.data, created: false, key }) : moved;
|
|
10713
10745
|
}
|
|
10714
|
-
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;
|
|
10715
10747
|
const intent = {
|
|
10716
10748
|
objective: input.objective?.trim() || `Work on ${label}`,
|
|
10717
10749
|
scope: { description: `the checkout (${label})`, paths: ["."], exclusions: [] },
|
|
@@ -10771,14 +10803,14 @@ class TaskStore {
|
|
|
10771
10803
|
const branch = key.ok ? key.data.branch : null;
|
|
10772
10804
|
if (!branch || !location.ok)
|
|
10773
10805
|
return success(null, null, null);
|
|
10774
|
-
const common =
|
|
10775
|
-
const heads =
|
|
10776
|
-
const headLog = location.data.git ?
|
|
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") : "";
|
|
10777
10809
|
const signatures = [
|
|
10778
|
-
|
|
10810
|
+
path9.join(heads, branch),
|
|
10779
10811
|
headLog,
|
|
10780
10812
|
heads,
|
|
10781
|
-
|
|
10813
|
+
path9.join(common, "packed-refs")
|
|
10782
10814
|
].map((file) => fileSignature(file) ?? "-");
|
|
10783
10815
|
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
10784
10816
|
let resolved = origins.get(slot);
|
|
@@ -10790,7 +10822,7 @@ class TaskStore {
|
|
|
10790
10822
|
aliases.push(rename.from);
|
|
10791
10823
|
const start = branchStart(location.data, branch, aliases);
|
|
10792
10824
|
const slash = start?.indexOf("/") ?? -1;
|
|
10793
|
-
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(
|
|
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)}"]`));
|
|
10794
10826
|
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
10795
10827
|
resolved = {
|
|
10796
10828
|
signature: signatures.join("|"),
|
|
@@ -11028,13 +11060,13 @@ class TaskStore {
|
|
|
11028
11060
|
if (!ready.ok)
|
|
11029
11061
|
return ready;
|
|
11030
11062
|
const dirs = this.paths();
|
|
11031
|
-
const recovery =
|
|
11032
|
-
if (
|
|
11063
|
+
const recovery = path9.join(this.root, ".workit", "recovery");
|
|
11064
|
+
if (fs10.existsSync(recovery)) {
|
|
11033
11065
|
let files = 0;
|
|
11034
11066
|
let bytes = 0;
|
|
11035
11067
|
try {
|
|
11036
|
-
for (const name of
|
|
11037
|
-
const stat =
|
|
11068
|
+
for (const name of fs10.readdirSync(recovery)) {
|
|
11069
|
+
const stat = fs10.lstatSync(path9.join(recovery, name));
|
|
11038
11070
|
if (!stat.isFile())
|
|
11039
11071
|
continue;
|
|
11040
11072
|
files += 1;
|
|
@@ -11043,17 +11075,17 @@ class TaskStore {
|
|
|
11043
11075
|
} catch {}
|
|
11044
11076
|
const remove = options.pruneRecovery === true && !dryRun;
|
|
11045
11077
|
if (remove)
|
|
11046
|
-
|
|
11078
|
+
fs10.rmSync(recovery, { recursive: true, force: true });
|
|
11047
11079
|
report.legacyRecovery = { path: recovery, files, bytes, removed: remove };
|
|
11048
11080
|
}
|
|
11049
|
-
if (!
|
|
11081
|
+
if (!fs10.existsSync(dirs.tasks))
|
|
11050
11082
|
return success(null, null, report);
|
|
11051
|
-
const names =
|
|
11083
|
+
const names = fs10.readdirSync(dirs.tasks).filter((name) => validId(name));
|
|
11052
11084
|
for (const name of names) {
|
|
11053
11085
|
const file = this.eventsPath(name);
|
|
11054
11086
|
let size = 0;
|
|
11055
11087
|
try {
|
|
11056
|
-
size =
|
|
11088
|
+
size = fs10.statSync(file).size;
|
|
11057
11089
|
} catch {
|
|
11058
11090
|
continue;
|
|
11059
11091
|
}
|
|
@@ -11081,16 +11113,16 @@ class TaskStore {
|
|
|
11081
11113
|
const references = new Set;
|
|
11082
11114
|
for (const name of names)
|
|
11083
11115
|
try {
|
|
11084
|
-
for (const match of
|
|
11116
|
+
for (const match of fs10.readFileSync(this.eventsPath(name), "utf8").matchAll(/"\$blob":"([0-9a-f]{64})"/g))
|
|
11085
11117
|
references.add(match[1]);
|
|
11086
11118
|
} catch {}
|
|
11087
|
-
if (
|
|
11088
|
-
for (const name of
|
|
11119
|
+
if (fs10.existsSync(dirs.blobs))
|
|
11120
|
+
for (const name of fs10.readdirSync(dirs.blobs)) {
|
|
11089
11121
|
const digest = name.replace(/\.json$/, "");
|
|
11090
|
-
const file =
|
|
11122
|
+
const file = path9.join(dirs.blobs, name);
|
|
11091
11123
|
let stat;
|
|
11092
11124
|
try {
|
|
11093
|
-
stat =
|
|
11125
|
+
stat = fs10.lstatSync(file);
|
|
11094
11126
|
} catch {
|
|
11095
11127
|
continue;
|
|
11096
11128
|
}
|
|
@@ -11101,11 +11133,11 @@ class TaskStore {
|
|
|
11101
11133
|
report.blobs.removed += 1;
|
|
11102
11134
|
report.blobs.removedBytes += stat.size;
|
|
11103
11135
|
if (!dryRun)
|
|
11104
|
-
|
|
11136
|
+
fs10.rmSync(file, { force: true });
|
|
11105
11137
|
}
|
|
11106
11138
|
return success(null, null, null);
|
|
11107
11139
|
};
|
|
11108
|
-
const swept = dryRun ? sweep() : this.withLock(
|
|
11140
|
+
const swept = dryRun ? sweep() : this.withLock(path9.join(dirs.dir, "gc.lock"), sweep);
|
|
11109
11141
|
if (!swept.ok)
|
|
11110
11142
|
return swept;
|
|
11111
11143
|
const temporaryDirs = [
|
|
@@ -11113,17 +11145,17 @@ class TaskStore {
|
|
|
11113
11145
|
dirs.tasks,
|
|
11114
11146
|
dirs.blobs,
|
|
11115
11147
|
dirs.checkout,
|
|
11116
|
-
...names.map((name) =>
|
|
11148
|
+
...names.map((name) => path9.join(dirs.tasks, name))
|
|
11117
11149
|
];
|
|
11118
11150
|
for (const directory of temporaryDirs) {
|
|
11119
|
-
if (!
|
|
11151
|
+
if (!fs10.existsSync(directory))
|
|
11120
11152
|
continue;
|
|
11121
|
-
for (const name of
|
|
11153
|
+
for (const name of fs10.readdirSync(directory)) {
|
|
11122
11154
|
if (!name.endsWith(".tmp") && !name.endsWith(".probe"))
|
|
11123
11155
|
continue;
|
|
11124
|
-
const file =
|
|
11156
|
+
const file = path9.join(directory, name);
|
|
11125
11157
|
try {
|
|
11126
|
-
const stat =
|
|
11158
|
+
const stat = fs10.lstatSync(file);
|
|
11127
11159
|
if (!stat.isFile() || nowMs - stat.mtimeMs <= STALE_TEMPORARY_MS)
|
|
11128
11160
|
continue;
|
|
11129
11161
|
} catch {
|
|
@@ -11131,37 +11163,37 @@ class TaskStore {
|
|
|
11131
11163
|
}
|
|
11132
11164
|
report.temporary.removed += 1;
|
|
11133
11165
|
if (!dryRun)
|
|
11134
|
-
|
|
11166
|
+
fs10.rmSync(file, { force: true });
|
|
11135
11167
|
}
|
|
11136
11168
|
}
|
|
11137
11169
|
return success(null, null, report);
|
|
11138
11170
|
}
|
|
11139
11171
|
paths() {
|
|
11140
11172
|
const location = this.location();
|
|
11141
|
-
const dir = location.ok ? location.data.dir :
|
|
11142
|
-
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));
|
|
11143
11175
|
return {
|
|
11144
11176
|
dir,
|
|
11145
11177
|
shared: location.ok ? location.data.shared : false,
|
|
11146
|
-
marker:
|
|
11178
|
+
marker: path9.join(dir, "store.json"),
|
|
11147
11179
|
checkout,
|
|
11148
|
-
workspace:
|
|
11149
|
-
checkoutLock:
|
|
11150
|
-
tasks:
|
|
11151
|
-
index:
|
|
11152
|
-
blobs:
|
|
11153
|
-
legacy:
|
|
11154
|
-
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")
|
|
11155
11187
|
};
|
|
11156
11188
|
}
|
|
11157
11189
|
eventsPath(taskId) {
|
|
11158
|
-
return
|
|
11190
|
+
return path9.join(this.paths().tasks, taskId, "events.jsonl");
|
|
11159
11191
|
}
|
|
11160
11192
|
snapshotPath(taskId) {
|
|
11161
|
-
return
|
|
11193
|
+
return path9.join(this.paths().tasks, taskId, "snapshot.json");
|
|
11162
11194
|
}
|
|
11163
11195
|
taskLockPath(taskId) {
|
|
11164
|
-
return
|
|
11196
|
+
return path9.join(this.paths().tasks, taskId, "lock");
|
|
11165
11197
|
}
|
|
11166
11198
|
ready(write = false) {
|
|
11167
11199
|
const location = this.location();
|
|
@@ -11171,7 +11203,7 @@ class TaskStore {
|
|
|
11171
11203
|
return success(null, null, null);
|
|
11172
11204
|
const dirs = this.paths();
|
|
11173
11205
|
try {
|
|
11174
|
-
const marker = JSON.parse(
|
|
11206
|
+
const marker = JSON.parse(fs10.readFileSync(dirs.marker, "utf8"));
|
|
11175
11207
|
if (typeof marker.version === "number" && marker.version > STORE_FORMAT)
|
|
11176
11208
|
return failure2("recovery_required", `the workit store at ${dirs.dir} has format ${marker.version}; upgrade Workit to use it`, { path: dirs.marker });
|
|
11177
11209
|
} catch {}
|
|
@@ -11200,21 +11232,21 @@ class TaskStore {
|
|
|
11200
11232
|
return {
|
|
11201
11233
|
read: (digest) => {
|
|
11202
11234
|
try {
|
|
11203
|
-
return JSON.parse(
|
|
11235
|
+
return JSON.parse(fs10.readFileSync(path9.join(dir, `${digest}.json`), "utf8"));
|
|
11204
11236
|
} catch (error) {
|
|
11205
11237
|
throw new LogDamage(`stored candidate ${digest} is missing or unreadable: ${String(error)}`);
|
|
11206
11238
|
}
|
|
11207
11239
|
},
|
|
11208
11240
|
write: (digest, value) => {
|
|
11209
|
-
const file =
|
|
11210
|
-
if (
|
|
11241
|
+
const file = path9.join(dir, `${digest}.json`);
|
|
11242
|
+
if (fs10.existsSync(file)) {
|
|
11211
11243
|
const stamp = new Date;
|
|
11212
11244
|
try {
|
|
11213
|
-
|
|
11245
|
+
fs10.utimesSync(file, stamp, stamp);
|
|
11214
11246
|
return;
|
|
11215
11247
|
} catch {}
|
|
11216
11248
|
}
|
|
11217
|
-
|
|
11249
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
11218
11250
|
replaceFile(file, JSON.stringify(value), true);
|
|
11219
11251
|
}
|
|
11220
11252
|
};
|
|
@@ -11296,7 +11328,7 @@ class TaskStore {
|
|
|
11296
11328
|
}
|
|
11297
11329
|
readSnapshot(taskId) {
|
|
11298
11330
|
try {
|
|
11299
|
-
const value = JSON.parse(
|
|
11331
|
+
const value = JSON.parse(fs10.readFileSync(this.snapshotPath(taskId), "utf8"));
|
|
11300
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))
|
|
11301
11333
|
return null;
|
|
11302
11334
|
return value;
|
|
@@ -11398,8 +11430,8 @@ class TaskStore {
|
|
|
11398
11430
|
}
|
|
11399
11431
|
}
|
|
11400
11432
|
openTask(record, type, key, legacy = null) {
|
|
11401
|
-
const dir =
|
|
11402
|
-
|
|
11433
|
+
const dir = path9.join(this.paths().tasks, record.id);
|
|
11434
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
11403
11435
|
const blobs = this.blobStore();
|
|
11404
11436
|
const bound = key ? { ...key, boundAt: key.boundAt ?? new Date().toISOString() } : null;
|
|
11405
11437
|
const event = this.event(record.id, 1, type, {
|
|
@@ -11451,11 +11483,11 @@ class TaskStore {
|
|
|
11451
11483
|
} catch {}
|
|
11452
11484
|
if (!latest || latest.events[0]?.id !== last.id)
|
|
11453
11485
|
return failure2("busy", "the task log changed during compaction; retry", { taskId });
|
|
11454
|
-
const appended =
|
|
11486
|
+
const appended = fs10.readFileSync(file).subarray(log.end, latest.end);
|
|
11455
11487
|
const bytes = Buffer.concat([Buffer.from(prefix, "utf8"), appended]);
|
|
11456
11488
|
replaceFile(file, bytes.toString("utf8"), true);
|
|
11457
11489
|
try {
|
|
11458
|
-
|
|
11490
|
+
fs10.rmSync(this.snapshotPath(taskId), { force: true });
|
|
11459
11491
|
} catch {}
|
|
11460
11492
|
const reloaded = this.load(taskId);
|
|
11461
11493
|
if (reloaded.ok && reloaded.data)
|
|
@@ -11468,7 +11500,7 @@ class TaskStore {
|
|
|
11468
11500
|
}
|
|
11469
11501
|
countEvents(file) {
|
|
11470
11502
|
try {
|
|
11471
|
-
const bytes =
|
|
11503
|
+
const bytes = fs10.readFileSync(file);
|
|
11472
11504
|
let count = 0;
|
|
11473
11505
|
for (let index = bytes.indexOf(10);index >= 0; index = bytes.indexOf(10, index + 1))
|
|
11474
11506
|
count += 1;
|
|
@@ -11490,8 +11522,8 @@ class TaskStore {
|
|
|
11490
11522
|
return bound.ok ? success(null, null, bound.data[0] ?? null) : bound;
|
|
11491
11523
|
}
|
|
11492
11524
|
withKeyLock(key, operation) {
|
|
11493
|
-
const digest =
|
|
11494
|
-
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);
|
|
11495
11527
|
}
|
|
11496
11528
|
bindLocked(taskId, key, timestamp) {
|
|
11497
11529
|
const current = this.readWorkspace();
|
|
@@ -11632,7 +11664,7 @@ class TaskStore {
|
|
|
11632
11664
|
writeWorkspace(record) {
|
|
11633
11665
|
const file = this.paths().workspace;
|
|
11634
11666
|
try {
|
|
11635
|
-
|
|
11667
|
+
fs10.mkdirSync(path9.dirname(file), { recursive: true });
|
|
11636
11668
|
replaceFile(file, `${canonicalJson(record)}
|
|
11637
11669
|
`, true);
|
|
11638
11670
|
return success(null, null, null);
|
|
@@ -11642,7 +11674,7 @@ class TaskStore {
|
|
|
11642
11674
|
}
|
|
11643
11675
|
readIndex() {
|
|
11644
11676
|
try {
|
|
11645
|
-
const value = JSON.parse(
|
|
11677
|
+
const value = JSON.parse(fs10.readFileSync(this.paths().index, "utf8"));
|
|
11646
11678
|
if (!isObject4(value) || value.version !== INDEX_VERSION || !isObject4(value.tasks))
|
|
11647
11679
|
return {};
|
|
11648
11680
|
const tasks = {};
|
|
@@ -11656,7 +11688,7 @@ class TaskStore {
|
|
|
11656
11688
|
}
|
|
11657
11689
|
writeIndex(entries) {
|
|
11658
11690
|
try {
|
|
11659
|
-
|
|
11691
|
+
fs10.mkdirSync(this.paths().tasks, { recursive: true });
|
|
11660
11692
|
replaceFile(this.paths().index, JSON.stringify({
|
|
11661
11693
|
version: INDEX_VERSION,
|
|
11662
11694
|
tasks: Object.fromEntries(entries.map((entry) => [entry.id, entry]))
|
|
@@ -11710,23 +11742,23 @@ class TaskStore {
|
|
|
11710
11742
|
return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
|
|
11711
11743
|
}
|
|
11712
11744
|
v2Dirs() {
|
|
11713
|
-
return [...new Set([this.root, this.origin])].map((dir) =>
|
|
11745
|
+
return [...new Set([this.root, this.origin])].map((dir) => path9.join(dir, ".workit"));
|
|
11714
11746
|
}
|
|
11715
11747
|
pendingMigrations() {
|
|
11716
11748
|
return this.v2Dirs().filter((dir) => {
|
|
11717
|
-
const raw = this.readV2Workspace(
|
|
11749
|
+
const raw = this.readV2Workspace(path9.join(dir, "workspace.json"));
|
|
11718
11750
|
return raw !== null && raw !== TOMBSTONE;
|
|
11719
11751
|
});
|
|
11720
11752
|
}
|
|
11721
11753
|
migrateV2(v2) {
|
|
11722
11754
|
const dirs = this.paths();
|
|
11723
|
-
const v2Workspace =
|
|
11724
|
-
const v2Tasks =
|
|
11725
|
-
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");
|
|
11726
11758
|
const raw = this.readV2Workspace(v2Workspace);
|
|
11727
11759
|
if (raw === null || raw === TOMBSTONE)
|
|
11728
11760
|
return success(null, null, null);
|
|
11729
|
-
return this.withLock(dirs.checkoutLock, () => this.withLock(
|
|
11761
|
+
return this.withLock(dirs.checkoutLock, () => this.withLock(path9.join(v2, "metadata.lock"), () => {
|
|
11730
11762
|
const workspace = this.readV2Workspace(v2Workspace);
|
|
11731
11763
|
if (workspace === null || workspace === TOMBSTONE)
|
|
11732
11764
|
return success(null, null, null);
|
|
@@ -11735,7 +11767,7 @@ class TaskStore {
|
|
|
11735
11767
|
return failure2("recovery_required", `cannot migrate the 2.x store at ${v2}: ${parsedWorkspace.error}; move it aside to start fresh`, { path: v2Workspace });
|
|
11736
11768
|
let files = [];
|
|
11737
11769
|
try {
|
|
11738
|
-
files =
|
|
11770
|
+
files = fs10.readdirSync(v2Tasks).filter((name) => name.endsWith(".json") && validId(name.slice(0, -5))).filter((name) => !this.isStub(path9.join(v2Tasks, name)));
|
|
11739
11771
|
} catch {}
|
|
11740
11772
|
const current = this.readWorkspace();
|
|
11741
11773
|
if (!current.ok)
|
|
@@ -11753,22 +11785,22 @@ class TaskStore {
|
|
|
11753
11785
|
let skipped = 0;
|
|
11754
11786
|
const done = new Map;
|
|
11755
11787
|
try {
|
|
11756
|
-
|
|
11788
|
+
fs10.mkdirSync(path9.join(legacy, "tasks"), { recursive: true });
|
|
11757
11789
|
for (const name of ["workspace.json", "index.json"])
|
|
11758
|
-
if (
|
|
11759
|
-
|
|
11790
|
+
if (fs10.existsSync(path9.join(v2, name)))
|
|
11791
|
+
fs10.copyFileSync(path9.join(v2, name), path9.join(legacy, name));
|
|
11760
11792
|
for (const name of files) {
|
|
11761
11793
|
const id = name.slice(0, -5);
|
|
11762
|
-
const file =
|
|
11763
|
-
const bytes =
|
|
11794
|
+
const file = path9.join(v2Tasks, name);
|
|
11795
|
+
const bytes = fs10.readFileSync(file, "utf8");
|
|
11764
11796
|
const digest = sha256(bytes);
|
|
11765
|
-
|
|
11797
|
+
fs10.writeFileSync(path9.join(legacy, "tasks", name), bytes);
|
|
11766
11798
|
const parsed = this.parseRecord(bytes, taskRecordSchema, "2.x task");
|
|
11767
11799
|
if (!parsed.ok || parsed.data.id !== id)
|
|
11768
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 });
|
|
11769
11801
|
const record = { ...parsed.data, workspaceId: target.id };
|
|
11770
|
-
const origin = { path:
|
|
11771
|
-
if (!
|
|
11802
|
+
const origin = { path: path9.join(legacy, "tasks", name), digest };
|
|
11803
|
+
if (!fs10.existsSync(this.eventsPath(id))) {
|
|
11772
11804
|
this.openTask(record, "migrated.from_v2", null, origin);
|
|
11773
11805
|
migrated += 1;
|
|
11774
11806
|
} else {
|
|
@@ -11803,8 +11835,8 @@ class TaskStore {
|
|
|
11803
11835
|
try {
|
|
11804
11836
|
let complete = true;
|
|
11805
11837
|
for (const [name, digest] of done) {
|
|
11806
|
-
const file =
|
|
11807
|
-
if (sha256(
|
|
11838
|
+
const file = path9.join(v2Tasks, name);
|
|
11839
|
+
if (sha256(fs10.readFileSync(file, "utf8")) !== digest) {
|
|
11808
11840
|
complete = false;
|
|
11809
11841
|
continue;
|
|
11810
11842
|
}
|
|
@@ -11814,7 +11846,7 @@ class TaskStore {
|
|
|
11814
11846
|
return failure2("busy", `the 2.x store at ${v2} changed during migration; retry`, {
|
|
11815
11847
|
path: v2
|
|
11816
11848
|
});
|
|
11817
|
-
|
|
11849
|
+
fs10.rmSync(path9.join(v2, "index.json"), { force: true });
|
|
11818
11850
|
this.writeTombstone(v2, target.id);
|
|
11819
11851
|
} catch (error) {
|
|
11820
11852
|
return failure2("storage_error", `cannot finish the 2.x migration: ${String(error)}`, {
|
|
@@ -11834,45 +11866,45 @@ class TaskStore {
|
|
|
11834
11866
|
}
|
|
11835
11867
|
adoptLocalStore() {
|
|
11836
11868
|
const dirs = this.paths();
|
|
11837
|
-
const marker =
|
|
11838
|
-
if (!dirs.shared || !
|
|
11869
|
+
const marker = path9.join(dirs.v2, "store.json");
|
|
11870
|
+
if (!dirs.shared || !fs10.existsSync(marker))
|
|
11839
11871
|
return success(null, null, null);
|
|
11840
11872
|
return this.withLock(dirs.checkoutLock, () => {
|
|
11841
|
-
if (!
|
|
11873
|
+
if (!fs10.existsSync(marker))
|
|
11842
11874
|
return success(null, null, null);
|
|
11843
11875
|
let tasks = 0;
|
|
11844
11876
|
try {
|
|
11845
11877
|
const merge = (from, to) => {
|
|
11846
|
-
if (!
|
|
11847
|
-
|
|
11878
|
+
if (!fs10.existsSync(to)) {
|
|
11879
|
+
fs10.mkdirSync(path9.dirname(to), { recursive: true });
|
|
11848
11880
|
try {
|
|
11849
|
-
|
|
11881
|
+
fs10.renameSync(from, to);
|
|
11850
11882
|
} catch {
|
|
11851
|
-
|
|
11852
|
-
|
|
11883
|
+
fs10.cpSync(from, to, { recursive: true, errorOnExist: true, force: false });
|
|
11884
|
+
fs10.rmSync(from, { recursive: true, force: true });
|
|
11853
11885
|
}
|
|
11854
11886
|
return;
|
|
11855
11887
|
}
|
|
11856
|
-
if (
|
|
11857
|
-
for (const name of
|
|
11858
|
-
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));
|
|
11859
11891
|
};
|
|
11860
11892
|
for (const sub of ["tasks", "checkouts", "blobs", "legacy"]) {
|
|
11861
|
-
const from =
|
|
11862
|
-
if (!
|
|
11893
|
+
const from = path9.join(dirs.v2, sub);
|
|
11894
|
+
if (!fs10.existsSync(from))
|
|
11863
11895
|
continue;
|
|
11864
11896
|
if (sub === "tasks") {
|
|
11865
|
-
for (const name of
|
|
11866
|
-
if (validId(name) && !
|
|
11897
|
+
for (const name of fs10.readdirSync(from))
|
|
11898
|
+
if (validId(name) && !fs10.existsSync(path9.join(dirs.dir, sub, name)))
|
|
11867
11899
|
tasks += 1;
|
|
11868
11900
|
}
|
|
11869
|
-
merge(from,
|
|
11901
|
+
merge(from, path9.join(dirs.dir, sub));
|
|
11870
11902
|
}
|
|
11871
|
-
const ledger =
|
|
11872
|
-
if (
|
|
11873
|
-
merge(ledger,
|
|
11874
|
-
|
|
11875
|
-
|
|
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"));
|
|
11876
11908
|
} catch (error) {
|
|
11877
11909
|
return failure2("storage_error", `cannot move ${dirs.v2} into ${dirs.dir}: ${String(error)}`, {
|
|
11878
11910
|
path: dirs.v2
|
|
@@ -11892,7 +11924,7 @@ class TaskStore {
|
|
|
11892
11924
|
readV2Workspace(file) {
|
|
11893
11925
|
let bytes;
|
|
11894
11926
|
try {
|
|
11895
|
-
bytes =
|
|
11927
|
+
bytes = fs10.readFileSync(file, "utf8");
|
|
11896
11928
|
} catch {
|
|
11897
11929
|
return null;
|
|
11898
11930
|
}
|
|
@@ -11910,7 +11942,7 @@ class TaskStore {
|
|
|
11910
11942
|
schemaVersion: SCHEMA_VERSION,
|
|
11911
11943
|
id: workspaceId ?? newId(),
|
|
11912
11944
|
revision: newRevision(),
|
|
11913
|
-
root:
|
|
11945
|
+
root: path9.dirname(v2),
|
|
11914
11946
|
runtime: { createdWith: version, updatedWith: version },
|
|
11915
11947
|
writer: null,
|
|
11916
11948
|
store: {
|
|
@@ -11921,12 +11953,12 @@ class TaskStore {
|
|
|
11921
11953
|
},
|
|
11922
11954
|
critical: ["store"]
|
|
11923
11955
|
};
|
|
11924
|
-
|
|
11925
|
-
const ignore =
|
|
11926
|
-
if (!
|
|
11927
|
-
|
|
11956
|
+
fs10.mkdirSync(v2, { recursive: true });
|
|
11957
|
+
const ignore = path9.join(v2, ".gitignore");
|
|
11958
|
+
if (!fs10.existsSync(ignore))
|
|
11959
|
+
fs10.writeFileSync(ignore, `*
|
|
11928
11960
|
`);
|
|
11929
|
-
replaceFile(
|
|
11961
|
+
replaceFile(path9.join(v2, "workspace.json"), `${JSON.stringify(marker, null, 2)}
|
|
11930
11962
|
`, true);
|
|
11931
11963
|
}
|
|
11932
11964
|
stubFor(taskId) {
|
|
@@ -11942,7 +11974,7 @@ class TaskStore {
|
|
|
11942
11974
|
}
|
|
11943
11975
|
isStub(file) {
|
|
11944
11976
|
try {
|
|
11945
|
-
const value = JSON.parse(
|
|
11977
|
+
const value = JSON.parse(fs10.readFileSync(file, "utf8"));
|
|
11946
11978
|
return isObject4(value) && isObject4(value.store) && value.store.format === "workit-store";
|
|
11947
11979
|
} catch {
|
|
11948
11980
|
return false;
|
|
@@ -11950,32 +11982,32 @@ class TaskStore {
|
|
|
11950
11982
|
}
|
|
11951
11983
|
writeTombstoneIfMissing() {
|
|
11952
11984
|
try {
|
|
11953
|
-
const v2 =
|
|
11954
|
-
if (
|
|
11985
|
+
const v2 = path9.join(this.root, ".workit");
|
|
11986
|
+
if (fs10.existsSync(path9.join(v2, "workspace.json")))
|
|
11955
11987
|
return;
|
|
11956
11988
|
let id = null;
|
|
11957
11989
|
try {
|
|
11958
|
-
id = JSON.parse(
|
|
11990
|
+
id = JSON.parse(fs10.readFileSync(this.paths().workspace, "utf8")).id ?? null;
|
|
11959
11991
|
} catch {}
|
|
11960
11992
|
this.writeTombstone(v2, id);
|
|
11961
11993
|
} catch {}
|
|
11962
11994
|
}
|
|
11963
11995
|
initializeStorage() {
|
|
11964
11996
|
const dirs = this.paths();
|
|
11965
|
-
retryTransient(() =>
|
|
11966
|
-
retryTransient(() =>
|
|
11967
|
-
if (!
|
|
11997
|
+
retryTransient(() => fs10.mkdirSync(dirs.checkout, { recursive: true }));
|
|
11998
|
+
retryTransient(() => fs10.mkdirSync(dirs.tasks, { recursive: true }));
|
|
11999
|
+
if (!fs10.existsSync(dirs.marker))
|
|
11968
12000
|
replaceFile(dirs.marker, `${JSON.stringify({ format: "workit-store", version: STORE_FORMAT, createdWith: runtimeVersion() })}
|
|
11969
12001
|
`, false);
|
|
11970
12002
|
if (!dirs.shared) {
|
|
11971
|
-
const ignore =
|
|
12003
|
+
const ignore = path9.join(dirs.dir, ".gitignore");
|
|
11972
12004
|
let current = null;
|
|
11973
12005
|
try {
|
|
11974
|
-
current =
|
|
12006
|
+
current = fs10.readFileSync(ignore, "utf8");
|
|
11975
12007
|
} catch {}
|
|
11976
12008
|
if (current !== `*
|
|
11977
12009
|
`)
|
|
11978
|
-
retryTransient(() =>
|
|
12010
|
+
retryTransient(() => fs10.writeFileSync(ignore, `*
|
|
11979
12011
|
`));
|
|
11980
12012
|
}
|
|
11981
12013
|
this.writeTombstoneIfMissing();
|
|
@@ -11991,7 +12023,7 @@ class TaskStore {
|
|
|
11991
12023
|
shouldReclaim: ({ payload, nowMs }) => {
|
|
11992
12024
|
let ageMs = null;
|
|
11993
12025
|
try {
|
|
11994
|
-
ageMs = nowMs -
|
|
12026
|
+
ageMs = nowMs - fs10.lstatSync(lockPath).mtimeMs;
|
|
11995
12027
|
} catch {}
|
|
11996
12028
|
return classifyLockOwner(payload, ageMs, localLockHost(), TASK_STORE_LOCK).state === "stale";
|
|
11997
12029
|
},
|
|
@@ -12028,7 +12060,7 @@ class TaskStore {
|
|
|
12028
12060
|
withLock(lockPath, operation) {
|
|
12029
12061
|
try {
|
|
12030
12062
|
this.initializeStorage();
|
|
12031
|
-
|
|
12063
|
+
fs10.mkdirSync(path9.dirname(lockPath), { recursive: true });
|
|
12032
12064
|
} catch (error) {
|
|
12033
12065
|
return failure2("storage_error", `unable to initialize store: ${String(error)}`, {
|
|
12034
12066
|
path: this.paths().dir
|
|
@@ -12068,18 +12100,18 @@ class TaskStore {
|
|
|
12068
12100
|
lockDirWritable(lockPath) {
|
|
12069
12101
|
const probe = `${lockPath}.${process.pid}.${randomUUID3()}.probe`;
|
|
12070
12102
|
try {
|
|
12071
|
-
|
|
12103
|
+
fs10.closeSync(fs10.openSync(probe, "wx", 384));
|
|
12072
12104
|
} catch {
|
|
12073
12105
|
return false;
|
|
12074
12106
|
}
|
|
12075
12107
|
try {
|
|
12076
|
-
|
|
12108
|
+
fs10.rmSync(probe, { force: true });
|
|
12077
12109
|
} catch {}
|
|
12078
12110
|
return true;
|
|
12079
12111
|
}
|
|
12080
12112
|
lockHolderPresent(lockPath) {
|
|
12081
12113
|
try {
|
|
12082
|
-
|
|
12114
|
+
fs10.lstatSync(lockPath);
|
|
12083
12115
|
return true;
|
|
12084
12116
|
} catch (error) {
|
|
12085
12117
|
return error?.code !== "ENOENT";
|
|
@@ -12091,12 +12123,12 @@ class TaskStore {
|
|
|
12091
12123
|
if (isTransientWindowsError(error) && !this.lockHolderPresent(lockPath) && !this.lockDirWritable(lockPath))
|
|
12092
12124
|
return failure2("storage_error", `cannot create the workit lock (${code}); no other Workit call holds it`, {
|
|
12093
12125
|
path: lockPath,
|
|
12094
|
-
guidance: `Check permissions and the read-only attribute on ${
|
|
12126
|
+
guidance: `Check permissions and the read-only attribute on ${path9.dirname(lockPath)}.`
|
|
12095
12127
|
});
|
|
12096
12128
|
if (code === "EEXIST" || code === "file_lock_timeout" || code === "file_lock_stale" || isTransientWindowsError(error)) {
|
|
12097
12129
|
let lock = null;
|
|
12098
12130
|
try {
|
|
12099
|
-
lock = parseMetadataLockOrNull(
|
|
12131
|
+
lock = parseMetadataLockOrNull(fs10.readFileSync(lockPath, "utf8"));
|
|
12100
12132
|
} catch {}
|
|
12101
12133
|
return failure2("busy", `workit lock is held by another Workit call${lock ? ` (pid ${lock.pid} on ${lock.host})` : ""}; retry shortly`, {
|
|
12102
12134
|
outcome: "not_started",
|
|
@@ -12111,17 +12143,17 @@ class TaskStore {
|
|
|
12111
12143
|
|
|
12112
12144
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12113
12145
|
import { existsSync as existsSync4 } from "node:fs";
|
|
12114
|
-
import
|
|
12146
|
+
import path14 from "node:path";
|
|
12115
12147
|
|
|
12116
12148
|
// packages/workit-core/src/ledger.ts
|
|
12117
12149
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12118
|
-
import { createHash as
|
|
12119
|
-
import
|
|
12120
|
-
import
|
|
12150
|
+
import { createHash as createHash9, randomBytes as randomBytes2 } from "node:crypto";
|
|
12151
|
+
import fs12 from "node:fs";
|
|
12152
|
+
import path13 from "node:path";
|
|
12121
12153
|
|
|
12122
12154
|
// packages/workit-core/src/core/vcs-config.ts
|
|
12123
|
-
import
|
|
12124
|
-
import
|
|
12155
|
+
import fs11 from "node:fs";
|
|
12156
|
+
import path12 from "node:path";
|
|
12125
12157
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12126
12158
|
|
|
12127
12159
|
// packages/workit-core/src/core/config.ts
|
|
@@ -12135,7 +12167,7 @@ import {
|
|
|
12135
12167
|
writeFileSync as writeFileSync2
|
|
12136
12168
|
} from "node:fs";
|
|
12137
12169
|
import os2 from "node:os";
|
|
12138
|
-
import
|
|
12170
|
+
import path10 from "node:path";
|
|
12139
12171
|
|
|
12140
12172
|
// packages/workit-core/src/core/boundary.ts
|
|
12141
12173
|
var EVENT = {
|
|
@@ -12179,7 +12211,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
12179
12211
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
12180
12212
|
};
|
|
12181
12213
|
};
|
|
12182
|
-
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");
|
|
12183
12215
|
var migratedDir = null;
|
|
12184
12216
|
var migrationFailed = false;
|
|
12185
12217
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -12189,7 +12221,7 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12189
12221
|
migratedDir = dir;
|
|
12190
12222
|
return dir;
|
|
12191
12223
|
}
|
|
12192
|
-
const legacy =
|
|
12224
|
+
const legacy = path10.join(process.env.XDG_CONFIG_HOME || path10.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
12193
12225
|
if (!existsSync3(legacy)) {
|
|
12194
12226
|
migratedDir = dir;
|
|
12195
12227
|
return dir;
|
|
@@ -12202,8 +12234,8 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
12202
12234
|
mkdirSync2(dir, { recursive: true });
|
|
12203
12235
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
12204
12236
|
for (const entry of readdirSync2(legacy, { withFileTypes: true })) {
|
|
12205
|
-
const src =
|
|
12206
|
-
const dest =
|
|
12237
|
+
const src = path10.join(legacy, entry.name);
|
|
12238
|
+
const dest = path10.join(dir, entry.name);
|
|
12207
12239
|
if (existsSync3(dest))
|
|
12208
12240
|
continue;
|
|
12209
12241
|
try {
|
|
@@ -12275,7 +12307,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
12275
12307
|
return { status: "valid", path: file, config };
|
|
12276
12308
|
};
|
|
12277
12309
|
var readConfigTyped = (dir) => {
|
|
12278
|
-
const file =
|
|
12310
|
+
const file = path10.join(dir ?? configDir(), "config.json");
|
|
12279
12311
|
return parseConfigResult(readSafe(file), file);
|
|
12280
12312
|
};
|
|
12281
12313
|
var readConfig = () => {
|
|
@@ -12284,7 +12316,7 @@ var readConfig = () => {
|
|
|
12284
12316
|
throw new Error(result.error);
|
|
12285
12317
|
return result.config ?? DEFAULTS;
|
|
12286
12318
|
};
|
|
12287
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
12319
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
12288
12320
|
var branchGlobPattern = (glob) => new RegExp(`^${glob.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`, "i");
|
|
12289
12321
|
var resolveBranchPolicy = (config, workspace, repoPreset) => {
|
|
12290
12322
|
const wp = workspace?.branchPolicy ?? {};
|
|
@@ -12326,9 +12358,9 @@ var resolveCommitPolicy = (config, workspace) => {
|
|
|
12326
12358
|
|
|
12327
12359
|
// packages/workit-core/src/core/workspaces.ts
|
|
12328
12360
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
12329
|
-
import { createHash as
|
|
12361
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
12330
12362
|
import { readFileSync as readFileSync3, realpathSync as realpathSync2 } from "node:fs";
|
|
12331
|
-
import
|
|
12363
|
+
import path11 from "node:path";
|
|
12332
12364
|
|
|
12333
12365
|
// packages/workit-core/src/core/branch-policy.ts
|
|
12334
12366
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
@@ -12345,6 +12377,7 @@ var branchNames = (workspaceRoot) => {
|
|
|
12345
12377
|
return new Set((r.stdout ?? "").split(`
|
|
12346
12378
|
`).filter(Boolean).map((ref) => ref.startsWith("refs/heads/") ? ref.slice("refs/heads/".length) : ref.slice("refs/remotes/origin/".length)));
|
|
12347
12379
|
};
|
|
12380
|
+
var productionBranchOf = (names) => names.has("main") ? "main" : names.has("master") ? "master" : "main";
|
|
12348
12381
|
var detectBranchPolicy = (workspaceRoot) => {
|
|
12349
12382
|
const names = branchNames(workspaceRoot);
|
|
12350
12383
|
const develop = names.has("develop");
|
|
@@ -12453,7 +12486,7 @@ var releaseTrackSchema = object({
|
|
|
12453
12486
|
context.addIssue({ code: "custom", path: [field], message: "entries must be unique" });
|
|
12454
12487
|
}
|
|
12455
12488
|
}
|
|
12456
|
-
if (track.versionSource?.kind === "package-json" && (
|
|
12489
|
+
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(".."))) {
|
|
12457
12490
|
context.addIssue({
|
|
12458
12491
|
code: "custom",
|
|
12459
12492
|
path: ["versionSource", "path"],
|
|
@@ -12513,7 +12546,7 @@ var workspacesFileSchema = object({ workspaces: array(workspaceConfigSchema).opt
|
|
|
12513
12546
|
names.add(workspace.name);
|
|
12514
12547
|
}
|
|
12515
12548
|
});
|
|
12516
|
-
var workspacesRevision = (contents) =>
|
|
12549
|
+
var workspacesRevision = (contents) => createHash7("sha256").update(contents ?? "<missing>").digest("hex");
|
|
12517
12550
|
var validateWorkspacesDocument = (parsed, file) => {
|
|
12518
12551
|
if (!isConfigObject(parsed)) {
|
|
12519
12552
|
return { status: "malformed", path: file, entries: [], error: `${file} is not a JSON object` };
|
|
@@ -12551,7 +12584,7 @@ var validateWorkspacesDocument = (parsed, file) => {
|
|
|
12551
12584
|
};
|
|
12552
12585
|
};
|
|
12553
12586
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
12554
|
-
const file =
|
|
12587
|
+
const file = path11.join(dir, "workspaces.json");
|
|
12555
12588
|
let raw;
|
|
12556
12589
|
try {
|
|
12557
12590
|
raw = readFileSync3(file, "utf8");
|
|
@@ -12662,9 +12695,9 @@ var mainCheckoutOf = (cwd) => {
|
|
|
12662
12695
|
let main = null;
|
|
12663
12696
|
const dirs = git(["rev-parse", "--path-format=absolute", "--git-dir", "--git-common-dir"]);
|
|
12664
12697
|
const [gitDir, common] = (dirs ?? "").trim().split(/\r?\n/u);
|
|
12665
|
-
if (gitDir && common &&
|
|
12698
|
+
if (gitDir && common && path11.resolve(gitDir) !== path11.resolve(common)) {
|
|
12666
12699
|
const listed = /^worktree (.+)$/mu.exec(git(["worktree", "list", "--porcelain"]) ?? "")?.[1];
|
|
12667
|
-
main = listed?.trim() || (
|
|
12700
|
+
main = listed?.trim() || (path11.basename(common) === ".git" ? path11.dirname(common) : common);
|
|
12668
12701
|
}
|
|
12669
12702
|
mainCheckouts.set(cwd, main);
|
|
12670
12703
|
return main;
|
|
@@ -12755,7 +12788,7 @@ var runtimeWorkspaceIndexSchema = object({
|
|
|
12755
12788
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
12756
12789
|
}).passthrough();
|
|
12757
12790
|
var matchRuntimeWorkspace = (cwd) => {
|
|
12758
|
-
const file =
|
|
12791
|
+
const file = path11.join(configDir(), "workspaces.json");
|
|
12759
12792
|
let raw;
|
|
12760
12793
|
try {
|
|
12761
12794
|
raw = readFileSync3(file, "utf8");
|
|
@@ -12830,7 +12863,7 @@ function workspaceDefaultTarget(cwd, globalDefault) {
|
|
|
12830
12863
|
}
|
|
12831
12864
|
|
|
12832
12865
|
// packages/workit-core/src/core/release-tracks.ts
|
|
12833
|
-
import { createHash as
|
|
12866
|
+
import { createHash as createHash8 } from "node:crypto";
|
|
12834
12867
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
12835
12868
|
class ReleaseTrackError extends Error {
|
|
12836
12869
|
}
|
|
@@ -13213,7 +13246,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13213
13246
|
]) ?? "",
|
|
13214
13247
|
git3(cwd, ["config", "--get", recordedBaseKey(branch)]) ?? ""
|
|
13215
13248
|
] : [];
|
|
13216
|
-
key =
|
|
13249
|
+
key = createHash8("sha256").update(JSON.stringify({ cwd, input, refs, head, own })).digest("hex");
|
|
13217
13250
|
const hit = memo2.get(key);
|
|
13218
13251
|
if (hit)
|
|
13219
13252
|
return hit;
|
|
@@ -13228,7 +13261,7 @@ function resolveTrackFor(cwd, options) {
|
|
|
13228
13261
|
}
|
|
13229
13262
|
|
|
13230
13263
|
// packages/workit-core/src/core/vcs-config.ts
|
|
13231
|
-
var workspacesPath = () =>
|
|
13264
|
+
var workspacesPath = () => path12.join(configDir(), "workspaces.json");
|
|
13232
13265
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
13233
13266
|
var remoteProvider = (cwd) => {
|
|
13234
13267
|
const r = spawnSync8("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -13245,7 +13278,7 @@ var readVcsConfig = () => {
|
|
|
13245
13278
|
const file = vcsConfigPath();
|
|
13246
13279
|
let raw;
|
|
13247
13280
|
try {
|
|
13248
|
-
raw =
|
|
13281
|
+
raw = fs11.readFileSync(file, "utf8");
|
|
13249
13282
|
} catch {
|
|
13250
13283
|
return { status: "missing", path: file, config: {} };
|
|
13251
13284
|
}
|
|
@@ -13330,7 +13363,7 @@ function vcsConfig(mode, cwd, options = {}) {
|
|
|
13330
13363
|
const prov = cfg[provider] ?? {};
|
|
13331
13364
|
const out = {
|
|
13332
13365
|
ok: true,
|
|
13333
|
-
configPath:
|
|
13366
|
+
configPath: path12.resolve(cfgPath),
|
|
13334
13367
|
provider,
|
|
13335
13368
|
defaultTargetBranch: workspaceDefault,
|
|
13336
13369
|
pr: cfg.pr ?? {},
|
|
@@ -13456,7 +13489,7 @@ function diffHash(cwd, base, head) {
|
|
|
13456
13489
|
], GIT_TIMEOUTS.worktree);
|
|
13457
13490
|
if (!diff.ok || !diff.stdout)
|
|
13458
13491
|
return null;
|
|
13459
|
-
const hash =
|
|
13492
|
+
const hash = createHash9("sha256");
|
|
13460
13493
|
for (const line of diff.stdout.split(`
|
|
13461
13494
|
`)) {
|
|
13462
13495
|
if (line.startsWith("index "))
|
|
@@ -13468,14 +13501,14 @@ function diffHash(cwd, base, head) {
|
|
|
13468
13501
|
return `sha256:${hash.digest("hex")}`;
|
|
13469
13502
|
}
|
|
13470
13503
|
function storeRoot(cwd) {
|
|
13471
|
-
const location = resolveStore(
|
|
13504
|
+
const location = resolveStore(path13.resolve(cwd));
|
|
13472
13505
|
if (location instanceof Error)
|
|
13473
13506
|
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");
|
|
13474
13507
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13475
13508
|
}
|
|
13476
13509
|
function ledgerPath(cwd) {
|
|
13477
13510
|
const root = storeRoot(cwd);
|
|
13478
|
-
return root.ok ? { ok: true, value:
|
|
13511
|
+
return root.ok ? { ok: true, value: path13.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13479
13512
|
}
|
|
13480
13513
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes2(6).toString("hex")}`;
|
|
13481
13514
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -13495,7 +13528,7 @@ var onNetworkFs = (dir) => {
|
|
|
13495
13528
|
if (process.platform !== "linux")
|
|
13496
13529
|
return false;
|
|
13497
13530
|
try {
|
|
13498
|
-
return isNetworkFsType(
|
|
13531
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
13499
13532
|
} catch {
|
|
13500
13533
|
return false;
|
|
13501
13534
|
}
|
|
@@ -13505,15 +13538,15 @@ var LOCK_STALE_MS = 30000;
|
|
|
13505
13538
|
var LOCK_OWNER = "owner";
|
|
13506
13539
|
var lockToken = (lock) => {
|
|
13507
13540
|
try {
|
|
13508
|
-
return
|
|
13541
|
+
return fs12.readFileSync(path13.join(lock, LOCK_OWNER), "utf8");
|
|
13509
13542
|
} catch {
|
|
13510
13543
|
return null;
|
|
13511
13544
|
}
|
|
13512
13545
|
};
|
|
13513
13546
|
var lockAgeMs = (lock) => {
|
|
13514
|
-
for (const target of [
|
|
13547
|
+
for (const target of [path13.join(lock, LOCK_OWNER), lock]) {
|
|
13515
13548
|
try {
|
|
13516
|
-
return Date.now() -
|
|
13549
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
13517
13550
|
} catch {}
|
|
13518
13551
|
}
|
|
13519
13552
|
return null;
|
|
@@ -13521,14 +13554,14 @@ var lockAgeMs = (lock) => {
|
|
|
13521
13554
|
var ledgerLock = {
|
|
13522
13555
|
acquire(lock) {
|
|
13523
13556
|
try {
|
|
13524
|
-
|
|
13557
|
+
fs12.mkdirSync(lock);
|
|
13525
13558
|
} catch (error) {
|
|
13526
13559
|
if (error.code === "EEXIST")
|
|
13527
13560
|
return null;
|
|
13528
13561
|
throw error;
|
|
13529
13562
|
}
|
|
13530
13563
|
const token = `${process.pid}-${randomBytes2(8).toString("hex")}`;
|
|
13531
|
-
|
|
13564
|
+
fs12.writeFileSync(path13.join(lock, LOCK_OWNER), token);
|
|
13532
13565
|
return token;
|
|
13533
13566
|
},
|
|
13534
13567
|
release(lock, token) {
|
|
@@ -13536,35 +13569,35 @@ var ledgerLock = {
|
|
|
13536
13569
|
return false;
|
|
13537
13570
|
const tomb = `${lock}.release-${randomBytes2(6).toString("hex")}`;
|
|
13538
13571
|
try {
|
|
13539
|
-
|
|
13572
|
+
fs12.renameSync(lock, tomb);
|
|
13540
13573
|
} catch {
|
|
13541
13574
|
return false;
|
|
13542
13575
|
}
|
|
13543
13576
|
if (lockToken(tomb) !== token) {
|
|
13544
13577
|
try {
|
|
13545
|
-
|
|
13578
|
+
fs12.renameSync(tomb, lock);
|
|
13546
13579
|
} catch {}
|
|
13547
13580
|
return false;
|
|
13548
13581
|
}
|
|
13549
|
-
|
|
13582
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13550
13583
|
return true;
|
|
13551
13584
|
},
|
|
13552
13585
|
token: lockToken,
|
|
13553
13586
|
reap(lock, observed) {
|
|
13554
13587
|
const tomb = `${lock}.reap-${randomBytes2(6).toString("hex")}`;
|
|
13555
13588
|
try {
|
|
13556
|
-
|
|
13589
|
+
fs12.renameSync(lock, tomb);
|
|
13557
13590
|
} catch {
|
|
13558
13591
|
return false;
|
|
13559
13592
|
}
|
|
13560
13593
|
if (lockToken(tomb) === observed) {
|
|
13561
|
-
|
|
13594
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13562
13595
|
return true;
|
|
13563
13596
|
}
|
|
13564
13597
|
try {
|
|
13565
|
-
|
|
13598
|
+
fs12.renameSync(tomb, lock);
|
|
13566
13599
|
} catch {
|
|
13567
|
-
|
|
13600
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
13568
13601
|
}
|
|
13569
13602
|
return false;
|
|
13570
13603
|
}
|
|
@@ -13591,14 +13624,14 @@ function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
|
13591
13624
|
ledgerLock.release(lock, token);
|
|
13592
13625
|
}
|
|
13593
13626
|
}
|
|
13594
|
-
var sha2562 = (text) =>
|
|
13627
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
13595
13628
|
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
13596
13629
|
${body}`);
|
|
13597
13630
|
var TAIL_BYTES = 64 * 1024;
|
|
13598
13631
|
var lastLine = (fd, size) => {
|
|
13599
13632
|
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
13600
13633
|
const buffer = Buffer.alloc(span);
|
|
13601
|
-
|
|
13634
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
13602
13635
|
const end = buffer.lastIndexOf(10);
|
|
13603
13636
|
if (end < 0) {
|
|
13604
13637
|
if (span === size)
|
|
@@ -13629,13 +13662,13 @@ var lastLine = (fd, size) => {
|
|
|
13629
13662
|
};
|
|
13630
13663
|
function appendLine(file, build, options) {
|
|
13631
13664
|
const write = () => {
|
|
13632
|
-
const fd =
|
|
13665
|
+
const fd = fs12.openSync(file, "a+");
|
|
13633
13666
|
try {
|
|
13634
|
-
const size =
|
|
13667
|
+
const size = fs12.fstatSync(fd).size;
|
|
13635
13668
|
let fence = "";
|
|
13636
13669
|
if (size > 0) {
|
|
13637
13670
|
const last = Buffer.alloc(1);
|
|
13638
|
-
|
|
13671
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
13639
13672
|
if (last[0] !== 10)
|
|
13640
13673
|
fence = `
|
|
13641
13674
|
`;
|
|
@@ -13644,17 +13677,17 @@ function appendLine(file, build, options) {
|
|
|
13644
13677
|
if (!line.ok)
|
|
13645
13678
|
return line;
|
|
13646
13679
|
const bytes = Buffer.from(fence + line.value);
|
|
13647
|
-
const written =
|
|
13680
|
+
const written = fs12.writeSync(fd, bytes);
|
|
13648
13681
|
if (written !== bytes.length)
|
|
13649
13682
|
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
13650
13683
|
return { ok: true, value: undefined };
|
|
13651
13684
|
} finally {
|
|
13652
|
-
|
|
13685
|
+
fs12.closeSync(fd);
|
|
13653
13686
|
}
|
|
13654
13687
|
};
|
|
13655
13688
|
try {
|
|
13656
|
-
|
|
13657
|
-
if (!options.forceLock && !onNetworkFs(
|
|
13689
|
+
fs12.mkdirSync(path13.dirname(file), { recursive: true });
|
|
13690
|
+
if (!options.forceLock && !onNetworkFs(path13.dirname(file)))
|
|
13658
13691
|
return write();
|
|
13659
13692
|
const locked = withLedgerLock(file, write);
|
|
13660
13693
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -13726,7 +13759,7 @@ function readLedger(cwd, options = {}) {
|
|
|
13726
13759
|
return file;
|
|
13727
13760
|
let text = "";
|
|
13728
13761
|
try {
|
|
13729
|
-
text =
|
|
13762
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
13730
13763
|
} catch (error) {
|
|
13731
13764
|
if (error.code === "ENOENT")
|
|
13732
13765
|
return {
|
|
@@ -13920,11 +13953,20 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
|
|
|
13920
13953
|
sessions.add(session);
|
|
13921
13954
|
return sessions;
|
|
13922
13955
|
}
|
|
13956
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
13957
|
+
var authoredBy = (authors, session) => {
|
|
13958
|
+
if (authors.has(session))
|
|
13959
|
+
return true;
|
|
13960
|
+
for (const author of authors)
|
|
13961
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
13962
|
+
return true;
|
|
13963
|
+
return false;
|
|
13964
|
+
};
|
|
13923
13965
|
var independenceReasons = (row, authors) => {
|
|
13924
13966
|
const reasons = [];
|
|
13925
13967
|
if (!row.actor.session)
|
|
13926
13968
|
reasons.push("no_session");
|
|
13927
|
-
else if (authors
|
|
13969
|
+
else if (authoredBy(authors, row.actor.session))
|
|
13928
13970
|
reasons.push("author_session");
|
|
13929
13971
|
if (row.self === true && !reasons.includes("no_session"))
|
|
13930
13972
|
reasons.push("self");
|
|
@@ -14208,7 +14250,7 @@ var productDecision = (task, ledger) => {
|
|
|
14208
14250
|
};
|
|
14209
14251
|
};
|
|
14210
14252
|
var planRecorded = (task, root) => {
|
|
14211
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(
|
|
14253
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync4(path14.resolve(root, ref.path)));
|
|
14212
14254
|
if (cited)
|
|
14213
14255
|
return {
|
|
14214
14256
|
met: true,
|
|
@@ -14267,15 +14309,15 @@ function writeBlockers(task, root) {
|
|
|
14267
14309
|
}
|
|
14268
14310
|
|
|
14269
14311
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
14270
|
-
var digestBytes = (value) =>
|
|
14271
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
14272
|
-
var relative = (root, candidate) =>
|
|
14312
|
+
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
14313
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path15.sep}`);
|
|
14314
|
+
var relative = (root, candidate) => path15.relative(root, candidate).split(path15.sep).join("/") || ".";
|
|
14273
14315
|
var canonicalPath = (value) => {
|
|
14274
14316
|
if (typeof value !== "string" || !value)
|
|
14275
14317
|
return null;
|
|
14276
14318
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
14277
14319
|
return null;
|
|
14278
|
-
const normalized =
|
|
14320
|
+
const normalized = path15.posix.normalize(value);
|
|
14279
14321
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
14280
14322
|
return null;
|
|
14281
14323
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -14323,21 +14365,21 @@ var gitPaths = (root) => {
|
|
|
14323
14365
|
git: !/not a git repository/i.test(stderr)
|
|
14324
14366
|
};
|
|
14325
14367
|
}
|
|
14326
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14368
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")) : [];
|
|
14327
14369
|
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
14328
14370
|
if (stagedDeleted.status !== 0) {
|
|
14329
14371
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
14330
14372
|
if (!/not a git repository/i.test(stderr))
|
|
14331
14373
|
return { paths, uncertain: true, git: true };
|
|
14332
14374
|
} else if (stagedDeleted.stdout) {
|
|
14333
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14375
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path15.sep).join("/")));
|
|
14334
14376
|
}
|
|
14335
14377
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
14336
14378
|
};
|
|
14337
14379
|
var walk = (root, directory, output) => {
|
|
14338
14380
|
let names;
|
|
14339
14381
|
try {
|
|
14340
|
-
names =
|
|
14382
|
+
names = fs13.readdirSync(directory);
|
|
14341
14383
|
} catch {
|
|
14342
14384
|
return true;
|
|
14343
14385
|
}
|
|
@@ -14345,10 +14387,10 @@ var walk = (root, directory, output) => {
|
|
|
14345
14387
|
for (const name of names) {
|
|
14346
14388
|
if (name === ".git" || name === ".workit")
|
|
14347
14389
|
continue;
|
|
14348
|
-
const target =
|
|
14390
|
+
const target = path15.join(directory, name);
|
|
14349
14391
|
let stat;
|
|
14350
14392
|
try {
|
|
14351
|
-
stat =
|
|
14393
|
+
stat = fs13.lstatSync(target);
|
|
14352
14394
|
} catch {
|
|
14353
14395
|
uncertain = true;
|
|
14354
14396
|
continue;
|
|
@@ -14363,14 +14405,14 @@ var walk = (root, directory, output) => {
|
|
|
14363
14405
|
var scopeRoots = (root, scope) => {
|
|
14364
14406
|
const roots = [];
|
|
14365
14407
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
14366
|
-
const target =
|
|
14408
|
+
const target = path15.resolve(root, item);
|
|
14367
14409
|
if (!inside(root, target))
|
|
14368
14410
|
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` }] });
|
|
14369
14411
|
let ancestor = target;
|
|
14370
|
-
while (!
|
|
14371
|
-
ancestor =
|
|
14412
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
14413
|
+
ancestor = path15.dirname(ancestor);
|
|
14372
14414
|
try {
|
|
14373
|
-
if (!inside(root,
|
|
14415
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
14374
14416
|
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` }] });
|
|
14375
14417
|
} catch {
|
|
14376
14418
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -14382,7 +14424,7 @@ var scopeRoots = (root, scope) => {
|
|
|
14382
14424
|
function captureCandidate(root, scope, environment = []) {
|
|
14383
14425
|
let checkout;
|
|
14384
14426
|
try {
|
|
14385
|
-
checkout =
|
|
14427
|
+
checkout = fs13.realpathSync(root);
|
|
14386
14428
|
} catch {
|
|
14387
14429
|
return failure2("invalid_input", "candidate root does not exist");
|
|
14388
14430
|
}
|
|
@@ -14400,11 +14442,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14400
14442
|
if (item === ".git" || item === ".workit")
|
|
14401
14443
|
continue;
|
|
14402
14444
|
names.add(item);
|
|
14403
|
-
if (!
|
|
14445
|
+
if (!fs13.existsSync(target))
|
|
14404
14446
|
continue;
|
|
14405
14447
|
let stat;
|
|
14406
14448
|
try {
|
|
14407
|
-
stat =
|
|
14449
|
+
stat = fs13.lstatSync(target);
|
|
14408
14450
|
} catch {
|
|
14409
14451
|
uncertain = true;
|
|
14410
14452
|
continue;
|
|
@@ -14420,17 +14462,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
14420
14462
|
for (const item of [...names].toSorted()) {
|
|
14421
14463
|
if (!scopeMatches(item, normalizedScope))
|
|
14422
14464
|
continue;
|
|
14423
|
-
const target =
|
|
14465
|
+
const target = path15.join(checkout, item);
|
|
14424
14466
|
try {
|
|
14425
|
-
const stat =
|
|
14467
|
+
const stat = fs13.lstatSync(target);
|
|
14426
14468
|
if (stat.isSymbolicLink()) {
|
|
14427
|
-
const link =
|
|
14469
|
+
const link = fs13.readlinkSync(target);
|
|
14428
14470
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
14429
14471
|
} else if (stat.isFile()) {
|
|
14430
14472
|
files.push({
|
|
14431
14473
|
path: item,
|
|
14432
14474
|
kind: "file",
|
|
14433
|
-
digest: digestBytes(
|
|
14475
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
14434
14476
|
executable: (stat.mode & 73) !== 0
|
|
14435
14477
|
});
|
|
14436
14478
|
} else if (!stat.isDirectory()) {
|
|
@@ -14595,7 +14637,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
14595
14637
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
14596
14638
|
let root;
|
|
14597
14639
|
try {
|
|
14598
|
-
root =
|
|
14640
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
14599
14641
|
} catch {
|
|
14600
14642
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
14601
14643
|
}
|
|
@@ -14604,23 +14646,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14604
14646
|
continue;
|
|
14605
14647
|
if (!reference.digest)
|
|
14606
14648
|
return failure2("invalid_input", "document references require a byte digest");
|
|
14607
|
-
const target =
|
|
14608
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
14649
|
+
const target = path15.resolve(root, reference.path);
|
|
14650
|
+
if (target !== root && !target.startsWith(`${root}${path15.sep}`))
|
|
14609
14651
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14610
14652
|
fields: [
|
|
14611
14653
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
14612
14654
|
]
|
|
14613
14655
|
});
|
|
14614
14656
|
try {
|
|
14615
|
-
const below =
|
|
14657
|
+
const below = path15.relative(root, target);
|
|
14616
14658
|
let current = root;
|
|
14617
|
-
for (const segment of below.split(
|
|
14659
|
+
for (const segment of below.split(path15.sep)) {
|
|
14618
14660
|
if (!segment)
|
|
14619
14661
|
continue;
|
|
14620
|
-
current =
|
|
14621
|
-
const stat =
|
|
14622
|
-
const real =
|
|
14623
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
14662
|
+
current = path15.join(current, segment);
|
|
14663
|
+
const stat = fs13.lstatSync(current);
|
|
14664
|
+
const real = fs13.realpathSync(current);
|
|
14665
|
+
if (real !== root && !real.startsWith(`${root}${path15.sep}`))
|
|
14624
14666
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
14625
14667
|
fields: [
|
|
14626
14668
|
{
|
|
@@ -14636,7 +14678,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
14636
14678
|
if (current !== target && !stat.isDirectory())
|
|
14637
14679
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
14638
14680
|
}
|
|
14639
|
-
if (digestBytes(
|
|
14681
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
14640
14682
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
14641
14683
|
} catch {
|
|
14642
14684
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -15493,7 +15535,7 @@ var judgeInputSummary = (raw) => {
|
|
|
15493
15535
|
|
|
15494
15536
|
// packages/workit-core/src/autonomy.ts
|
|
15495
15537
|
import os3 from "node:os";
|
|
15496
|
-
import
|
|
15538
|
+
import path16 from "node:path";
|
|
15497
15539
|
var accountHome = () => {
|
|
15498
15540
|
try {
|
|
15499
15541
|
return os3.userInfo().homedir;
|
|
@@ -15502,10 +15544,10 @@ var accountHome = () => {
|
|
|
15502
15544
|
}
|
|
15503
15545
|
};
|
|
15504
15546
|
var grantsHome = { resolve: accountHome };
|
|
15505
|
-
var grantsDir = () =>
|
|
15547
|
+
var grantsDir = () => path16.join(grantsHome.resolve(), ".config", "workit");
|
|
15506
15548
|
var grantsOverride = () => {
|
|
15507
|
-
const configured =
|
|
15508
|
-
return configured ===
|
|
15549
|
+
const configured = path16.resolve(resolveConfigDir());
|
|
15550
|
+
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`;
|
|
15509
15551
|
};
|
|
15510
15552
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
15511
15553
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -17487,7 +17529,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
17487
17529
|
return null;
|
|
17488
17530
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
17489
17531
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
17490
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17532
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path17.resolve(store.root, ref.path)] : []));
|
|
17491
17533
|
const observedFiles = filesKey(files);
|
|
17492
17534
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
17493
17535
|
if (!compact.ok || !compact.data)
|
|
@@ -17556,7 +17598,7 @@ var turnContextText = (input, descriptor) => {
|
|
|
17556
17598
|
}
|
|
17557
17599
|
};
|
|
17558
17600
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
17559
|
-
var digestOf = (text) =>
|
|
17601
|
+
var digestOf = (text) => createHash11("sha256").update(text).digest("hex");
|
|
17560
17602
|
var turnMarker = (input) => {
|
|
17561
17603
|
if (!input.session.id)
|
|
17562
17604
|
return null;
|
|
@@ -17565,32 +17607,32 @@ var turnMarker = (input) => {
|
|
|
17565
17607
|
if (location instanceof Error)
|
|
17566
17608
|
return null;
|
|
17567
17609
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
17568
|
-
return
|
|
17610
|
+
return path17.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
17569
17611
|
} catch {
|
|
17570
17612
|
return null;
|
|
17571
17613
|
}
|
|
17572
17614
|
};
|
|
17573
17615
|
var writeTurnDigest = (file, digest) => {
|
|
17574
17616
|
try {
|
|
17575
|
-
|
|
17576
|
-
|
|
17617
|
+
fs14.mkdirSync(path17.dirname(file), { recursive: true });
|
|
17618
|
+
fs14.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
17577
17619
|
`);
|
|
17578
17620
|
} catch {}
|
|
17579
17621
|
};
|
|
17580
17622
|
var clearTurnDigest = (file) => {
|
|
17581
17623
|
try {
|
|
17582
|
-
|
|
17624
|
+
fs14.rmSync(file, { force: true });
|
|
17583
17625
|
} catch {}
|
|
17584
17626
|
};
|
|
17585
17627
|
var pruneTurnDigests = (dir, now) => {
|
|
17586
17628
|
try {
|
|
17587
|
-
for (const name of
|
|
17629
|
+
for (const name of fs14.readdirSync(dir)) {
|
|
17588
17630
|
if (!name.startsWith("turn-"))
|
|
17589
17631
|
continue;
|
|
17590
|
-
const file =
|
|
17632
|
+
const file = path17.join(dir, name);
|
|
17591
17633
|
try {
|
|
17592
|
-
if (now -
|
|
17593
|
-
|
|
17634
|
+
if (now - fs14.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
17635
|
+
fs14.rmSync(file, { force: true });
|
|
17594
17636
|
} catch {}
|
|
17595
17637
|
}
|
|
17596
17638
|
} catch {}
|
|
@@ -17606,7 +17648,7 @@ var changedTurnContext = (input, text) => {
|
|
|
17606
17648
|
return text;
|
|
17607
17649
|
const digest = digestOf(text);
|
|
17608
17650
|
try {
|
|
17609
|
-
const previous = JSON.parse(
|
|
17651
|
+
const previous = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
17610
17652
|
if (previous.digest === digest)
|
|
17611
17653
|
return null;
|
|
17612
17654
|
} catch {}
|
|
@@ -17617,14 +17659,26 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
17617
17659
|
const file = turnMarker(input);
|
|
17618
17660
|
if (!file)
|
|
17619
17661
|
return;
|
|
17620
|
-
pruneTurnDigests(
|
|
17662
|
+
pruneTurnDigests(path17.dirname(file), Date.now());
|
|
17621
17663
|
if (text)
|
|
17622
17664
|
writeTurnDigest(file, digestOf(text));
|
|
17623
17665
|
else
|
|
17624
17666
|
clearTurnDigest(file);
|
|
17625
17667
|
};
|
|
17668
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
17669
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
17670
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
17671
|
+
import path20 from "node:path";
|
|
17672
|
+
|
|
17673
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17674
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
17675
|
+
import { createHash as createHash14 } from "node:crypto";
|
|
17676
|
+
import fs16 from "node:fs";
|
|
17677
|
+
import os4 from "node:os";
|
|
17678
|
+
import path19 from "node:path";
|
|
17679
|
+
|
|
17626
17680
|
// packages/workit-core/src/core/branch.ts
|
|
17627
|
-
import { createHash as
|
|
17681
|
+
import { createHash as createHash12 } from "node:crypto";
|
|
17628
17682
|
var withReleaseTracks = (workspaceRoot, policy) => {
|
|
17629
17683
|
const read = workspaceReleaseTracks(workspaceRoot);
|
|
17630
17684
|
if (read.error)
|
|
@@ -17660,10 +17714,72 @@ function effectiveWorkspacePolicy(workspaceRoot, kind, profileName) {
|
|
|
17660
17714
|
provenance: { commitPolicy: selected.source }
|
|
17661
17715
|
};
|
|
17662
17716
|
}
|
|
17717
|
+
var KIND_SYNONYMS = {
|
|
17718
|
+
fix: "bugfix",
|
|
17719
|
+
fixes: "bugfix",
|
|
17720
|
+
bug: "bugfix",
|
|
17721
|
+
bugs: "bugfix",
|
|
17722
|
+
bugfixes: "bugfix",
|
|
17723
|
+
feat: "feature",
|
|
17724
|
+
features: "feature",
|
|
17725
|
+
chore: "feature",
|
|
17726
|
+
docs: "feature",
|
|
17727
|
+
doc: "feature",
|
|
17728
|
+
refactor: "feature",
|
|
17729
|
+
test: "feature",
|
|
17730
|
+
tests: "feature",
|
|
17731
|
+
ci: "feature",
|
|
17732
|
+
build: "feature",
|
|
17733
|
+
perf: "feature",
|
|
17734
|
+
style: "feature",
|
|
17735
|
+
deps: "feature",
|
|
17736
|
+
task: "feature",
|
|
17737
|
+
enhancement: "feature",
|
|
17738
|
+
hot: "hotfix",
|
|
17739
|
+
hotfixes: "hotfix",
|
|
17740
|
+
releases: "release",
|
|
17741
|
+
rel: "release"
|
|
17742
|
+
};
|
|
17743
|
+
var allowedKinds = (allowed) => [
|
|
17744
|
+
...new Set(allowed.map((pattern) => /^\^([\w.-]+)\\?\/\.\*\$$/u.exec(pattern.source)?.[1]?.toLowerCase()).filter((kind) => Boolean(kind)))
|
|
17745
|
+
];
|
|
17746
|
+
var editDistance = (a, b) => {
|
|
17747
|
+
const row = Array.from({ length: b.length + 1 }, (_, index) => index);
|
|
17748
|
+
for (let i = 1;i <= a.length; i++) {
|
|
17749
|
+
let previous = row[0];
|
|
17750
|
+
row[0] = i;
|
|
17751
|
+
for (let j = 1;j <= b.length; j++) {
|
|
17752
|
+
const saved = row[j];
|
|
17753
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, previous + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
17754
|
+
previous = saved;
|
|
17755
|
+
}
|
|
17756
|
+
}
|
|
17757
|
+
return row[b.length];
|
|
17758
|
+
};
|
|
17759
|
+
var suggestBranchName = (name, kinds) => {
|
|
17760
|
+
if (!kinds.length)
|
|
17761
|
+
return null;
|
|
17762
|
+
const slash = name.indexOf("/");
|
|
17763
|
+
const prefix = slash > 0 ? name.slice(0, slash).toLowerCase() : "";
|
|
17764
|
+
const rest = (slash > 0 ? name.slice(slash + 1) : name) || "name";
|
|
17765
|
+
const synonym = KIND_SYNONYMS[prefix];
|
|
17766
|
+
const near = kinds.map((kind) => ({ kind, distance: editDistance(prefix, kind) })).filter((entry) => prefix && entry.distance <= 2).toSorted((a, b) => a.distance - b.distance)[0]?.kind;
|
|
17767
|
+
const kind = (synonym && kinds.includes(synonym) ? synonym : null) ?? near ?? (kinds.includes("feature") ? "feature" : kinds[0]);
|
|
17768
|
+
return `${kind}/${rest}`;
|
|
17769
|
+
};
|
|
17770
|
+
var kindList = (kinds) => kinds.map((kind) => `${kind}/`).join(", ");
|
|
17771
|
+
var aliasNote = (kinds) => {
|
|
17772
|
+
const notes = [
|
|
17773
|
+
["fix", "bugfix"],
|
|
17774
|
+
["chore", "feature"]
|
|
17775
|
+
].filter(([, kind]) => kinds.includes(kind)).map(([alias, kind]) => `${alias}/ is ${kind}/`);
|
|
17776
|
+
return notes.length ? ` (${notes.join(", ")})` : "";
|
|
17777
|
+
};
|
|
17663
17778
|
var validateBranchNamePolicy = (name, policy, source) => {
|
|
17664
17779
|
const allowed = policy.allowed.map((pattern) => `/${pattern.source}/${pattern.flags}`);
|
|
17780
|
+
const kinds = allowedKinds(policy.allowed);
|
|
17665
17781
|
if (policy.protected.has(name.toLowerCase())) {
|
|
17666
|
-
const correction = `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17782
|
+
const correction = kinds.length ? `choose a non-protected branch of an allowed kind (${kindList(kinds)}), e.g. ${kinds[0]}/<slug>: workit git branch --kind ${kinds[0]} --slug <slug>` : `choose a non-protected branch matching one of: ${allowed.join(", ")}`;
|
|
17667
17783
|
return {
|
|
17668
17784
|
ok: false,
|
|
17669
17785
|
rule: "protected_ref",
|
|
@@ -17674,7 +17790,8 @@ var validateBranchNamePolicy = (name, policy, source) => {
|
|
|
17674
17790
|
};
|
|
17675
17791
|
}
|
|
17676
17792
|
if (!policy.allowed.some((pattern) => pattern.test(name))) {
|
|
17677
|
-
const
|
|
17793
|
+
const suggestion = suggestBranchName(name, kinds);
|
|
17794
|
+
const correction = suggestion && policy.allowed.some((pattern) => pattern.test(suggestion)) ? `use ${JSON.stringify(suggestion)}; the allowed kinds are ${kindList(kinds)} (workit git branch ${suggestion})` : `choose a branch matching one of: ${allowed.join(", ")}`;
|
|
17678
17795
|
return {
|
|
17679
17796
|
ok: false,
|
|
17680
17797
|
rule: "allowed_pattern",
|
|
@@ -17692,7 +17809,7 @@ var branchPolicySnapshotFor = (workspaceRoot) => {
|
|
|
17692
17809
|
const source = resolved.provenance.branchPolicy;
|
|
17693
17810
|
const allowed = branchPolicy.allowed.map((pattern) => `${pattern.source}/${pattern.flags}`).toSorted();
|
|
17694
17811
|
const protectedRefs = [...branchPolicy.protected].map((name) => name.toLowerCase()).toSorted();
|
|
17695
|
-
const fingerprint =
|
|
17812
|
+
const fingerprint = createHash12("sha256").update(JSON.stringify({ allowed, protectedRefs, source })).digest("hex");
|
|
17696
17813
|
return {
|
|
17697
17814
|
branchPolicy,
|
|
17698
17815
|
namePolicy: { allowed: branchPolicy.allowed, protected: branchPolicy.protected },
|
|
@@ -17717,31 +17834,87 @@ var validateBranchNameFor = (workspaceRoot, name) => {
|
|
|
17717
17834
|
}
|
|
17718
17835
|
};
|
|
17719
17836
|
var resolveBranchPolicyFor = (workspaceRoot, profileName) => effectiveWorkspacePolicy(workspaceRoot, "branch", profileName).branchPolicy;
|
|
17720
|
-
|
|
17721
|
-
|
|
17722
|
-
|
|
17723
|
-
|
|
17724
|
-
|
|
17725
|
-
|
|
17726
|
-
|
|
17837
|
+
var gitFlowOf = (root, integration) => {
|
|
17838
|
+
try {
|
|
17839
|
+
if (resolveBranchPolicyFor(root).preset !== "gitflow")
|
|
17840
|
+
return null;
|
|
17841
|
+
const selected = resolveRuntimeWorkspacePolicy(root, "branch", process.env.WORKFLOW_PROFILE?.trim() || undefined).policy;
|
|
17842
|
+
const names = branchNames(root);
|
|
17843
|
+
const production = productionBranchOf(names);
|
|
17844
|
+
const develop = selected?.developBranch?.trim() || (integration && integration.toLowerCase() !== production ? integration : "develop");
|
|
17845
|
+
return { develop, production, developExists: names.has(develop) };
|
|
17846
|
+
} catch {
|
|
17727
17847
|
return null;
|
|
17728
|
-
|
|
17729
|
-
|
|
17730
|
-
|
|
17848
|
+
}
|
|
17849
|
+
};
|
|
17850
|
+
var releaseBranchesOf = (cwd) => {
|
|
17851
|
+
try {
|
|
17852
|
+
const resolved = resolveBranchPolicyFor(cwd);
|
|
17853
|
+
const flow = resolved.preset === "gitflow" ? gitFlowOf(cwd) : null;
|
|
17854
|
+
return {
|
|
17855
|
+
preset: resolved.preset,
|
|
17856
|
+
branches: flow ? [flow.production, flow.develop] : [],
|
|
17857
|
+
production: flow ? flow.production : resolved.defaultTargetBranch
|
|
17858
|
+
};
|
|
17859
|
+
} catch {
|
|
17860
|
+
return { preset: "unknown", branches: [], production: null };
|
|
17861
|
+
}
|
|
17731
17862
|
};
|
|
17732
|
-
var
|
|
17733
|
-
|
|
17734
|
-
|
|
17863
|
+
var releaseBranch = (cwd, branch) => releaseBranchesOf(cwd).branches.some((name) => name.toLowerCase() === branch.toLowerCase());
|
|
17864
|
+
var branchGuidance = (cwd) => {
|
|
17865
|
+
try {
|
|
17866
|
+
const patterns = branchPolicySnapshotFor(cwd).namePolicy.allowed;
|
|
17867
|
+
const kinds = allowedKinds(patterns);
|
|
17868
|
+
const kindShaped = new Set(kinds.map((kind) => branchGlobPattern(`${kind}/*`).source));
|
|
17869
|
+
const anyName = patterns.some((pattern) => pattern.source === "^.*$");
|
|
17870
|
+
const others = patterns.filter((pattern) => !kindShaped.has(pattern.source)).map((pattern) => globOf(pattern));
|
|
17871
|
+
const allowed = anyName ? "Any non-protected branch name is allowed here." : `${kinds.length ? `Allowed branch kinds here: ${kindList(kinds)}${aliasNote(kinds)}` : "Allowed branch names here:"}${others.length ? `${kinds.length ? "; also" : ""} names matching ${others.join(", ")}` : ""}; anything else is denied.`;
|
|
17872
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
17873
|
+
const integration = resolved.ok === false ? null : String(resolved.defaultTargetBranch ?? "") || null;
|
|
17874
|
+
const flow = resolved.releaseTrack?.track ? null : gitFlowOf(cwd, integration);
|
|
17875
|
+
if (flow?.developExists)
|
|
17876
|
+
return `${allowed} git-flow: feature/, bugfix/ and release/ start from origin/${flow.develop}, hotfix/ from origin/${flow.production}; feature/ and bugfix/ land on ${flow.develop}, release/ and hotfix/ on ${flow.production}.`;
|
|
17877
|
+
const names = branchNames(cwd);
|
|
17878
|
+
const configured = String(resolved.baseBranch ?? integration ?? "") || null;
|
|
17879
|
+
const trunk = productionBranchOf(names);
|
|
17880
|
+
const base = configured && names.has(configured) ? `Branch from origin/${configured}.` : names.has(trunk) ? `Branch from origin/${trunk}${configured ? ` (the default target ${configured} does not exist here)` : ""}.` : "No base branch exists here yet; pass the base explicitly.";
|
|
17881
|
+
return `${allowed} ${base}`;
|
|
17882
|
+
} catch {
|
|
17883
|
+
return null;
|
|
17884
|
+
}
|
|
17735
17885
|
};
|
|
17886
|
+
var globOf = (pattern) => pattern.source.replace(/^\^|\$$/gu, "").replaceAll(".*", "*").replace(/\\(.)/gu, "$1");
|
|
17736
17887
|
|
|
17737
|
-
// packages/workit-core/src/hooks/
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17888
|
+
// packages/workit-core/src/hooks/session-marker.ts
|
|
17889
|
+
import { createHash as createHash13 } from "node:crypto";
|
|
17890
|
+
import fs15 from "node:fs";
|
|
17891
|
+
import path18 from "node:path";
|
|
17892
|
+
var sessionMarkerFile = (input, session, store) => {
|
|
17893
|
+
let dir = store;
|
|
17894
|
+
if (dir === undefined) {
|
|
17895
|
+
const location = resolveStore(input.cwd);
|
|
17896
|
+
if (location instanceof Error || !location.shared)
|
|
17897
|
+
return null;
|
|
17898
|
+
dir = location.dir;
|
|
17899
|
+
}
|
|
17900
|
+
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
17901
|
+
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
17902
|
+
};
|
|
17903
|
+
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
17904
|
+
var readSessionMarker = (file) => {
|
|
17905
|
+
try {
|
|
17906
|
+
const value = JSON.parse(fs15.readFileSync(file, "utf8"));
|
|
17907
|
+
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
17908
|
+
} catch {
|
|
17909
|
+
return { nudged: [], loaded: [], raw: [] };
|
|
17910
|
+
}
|
|
17911
|
+
};
|
|
17912
|
+
var writeSessionMarker = (file, marker) => {
|
|
17913
|
+
try {
|
|
17914
|
+
fs15.mkdirSync(path18.dirname(file), { recursive: true });
|
|
17915
|
+
fs15.writeFileSync(file, JSON.stringify(marker));
|
|
17916
|
+
} catch {}
|
|
17741
17917
|
};
|
|
17742
|
-
// packages/workit-core/src/hooks/write-gate.ts
|
|
17743
|
-
import { realpathSync as realpathSync5 } from "node:fs";
|
|
17744
|
-
import path17 from "node:path";
|
|
17745
17918
|
|
|
17746
17919
|
// packages/workit-core/src/hooks/shell-words.ts
|
|
17747
17920
|
var closingQuote = (command, start, dialect) => {
|
|
@@ -17866,295 +18039,81 @@ var segmentsOf = (command, dialect = "posix") => {
|
|
|
17866
18039
|
return segments;
|
|
17867
18040
|
};
|
|
17868
18041
|
|
|
17869
|
-
// packages/workit-core/src/hooks/
|
|
18042
|
+
// packages/workit-core/src/hooks/raw-git.ts
|
|
17870
18043
|
var NONE2 = { kind: "none" };
|
|
17871
|
-
var
|
|
17872
|
-
var
|
|
17873
|
-
|
|
17874
|
-
|
|
17875
|
-
|
|
17876
|
-
|
|
17877
|
-
|
|
17878
|
-
|
|
17879
|
-
|
|
17880
|
-
|
|
17881
|
-
|
|
17882
|
-
|
|
18044
|
+
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18045
|
+
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18046
|
+
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18047
|
+
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18048
|
+
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18049
|
+
var POPD = new Set(["popd", "pop-location"]);
|
|
18050
|
+
var GIT_VALUE_OPTIONS = new Set([
|
|
18051
|
+
"-c",
|
|
18052
|
+
"--git-dir",
|
|
18053
|
+
"--work-tree",
|
|
18054
|
+
"--namespace",
|
|
18055
|
+
"--exec-path",
|
|
18056
|
+
"--config-env",
|
|
18057
|
+
"--super-prefix"
|
|
18058
|
+
]);
|
|
18059
|
+
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18060
|
+
var joinDir = (base, next) => {
|
|
18061
|
+
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18062
|
+
return;
|
|
18063
|
+
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18064
|
+
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
17883
18065
|
};
|
|
17884
|
-
var
|
|
17885
|
-
const
|
|
17886
|
-
const
|
|
17887
|
-
if (
|
|
17888
|
-
return
|
|
17889
|
-
|
|
17890
|
-
const segments = posix.split("/");
|
|
17891
|
-
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18066
|
+
var cdTarget = (words) => {
|
|
18067
|
+
const args = words.slice(1);
|
|
18068
|
+
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18069
|
+
if (flag >= 0)
|
|
18070
|
+
return args[flag + 1] ?? null;
|
|
18071
|
+
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
17892
18072
|
};
|
|
17893
|
-
var
|
|
17894
|
-
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
17895
|
-
var editsOf = (input) => {
|
|
18073
|
+
var stripPrefixes = (input) => {
|
|
17896
18074
|
let words = input;
|
|
17897
|
-
|
|
17898
|
-
|
|
17899
|
-
|
|
17900
|
-
|
|
17901
|
-
|
|
17902
|
-
|
|
17903
|
-
|
|
17904
|
-
|
|
17905
|
-
|
|
17906
|
-
|
|
17907
|
-
|
|
17908
|
-
|
|
17909
|
-
|
|
17910
|
-
|
|
17911
|
-
|
|
17912
|
-
|
|
17913
|
-
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
|
|
17917
|
-
|
|
17918
|
-
|
|
17919
|
-
|
|
18075
|
+
for (;; ) {
|
|
18076
|
+
const first = words[0];
|
|
18077
|
+
if (first === undefined)
|
|
18078
|
+
return words;
|
|
18079
|
+
if (/^\w+=/.test(first))
|
|
18080
|
+
words = words.slice(1);
|
|
18081
|
+
else if (PREFIXES2.has(first)) {
|
|
18082
|
+
words = words.slice(1);
|
|
18083
|
+
while (words[0]?.startsWith("-"))
|
|
18084
|
+
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
18085
|
+
} else if (first === "timeout") {
|
|
18086
|
+
words = words.slice(1);
|
|
18087
|
+
while (words[0]?.startsWith("-"))
|
|
18088
|
+
words = words.slice(1);
|
|
18089
|
+
words = words.slice(1);
|
|
18090
|
+
} else
|
|
18091
|
+
return words;
|
|
18092
|
+
}
|
|
18093
|
+
};
|
|
18094
|
+
var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
18095
|
+
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
18096
|
+
const found = [];
|
|
18097
|
+
let dir = null;
|
|
18098
|
+
const groups = [];
|
|
18099
|
+
let stack = [];
|
|
18100
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
18101
|
+
if (segment.group === "open") {
|
|
18102
|
+
groups.push({ dir, stack: [...stack] });
|
|
18103
|
+
continue;
|
|
17920
18104
|
}
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17925
|
-
|
|
17926
|
-
return [];
|
|
17927
|
-
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
17928
|
-
const files = [];
|
|
17929
|
-
for (let index = 0;index < args.length; index++) {
|
|
17930
|
-
if (args[index] === "-e" || args[index] === "-f")
|
|
17931
|
-
index++;
|
|
17932
|
-
else if (!args[index].startsWith("-"))
|
|
17933
|
-
files.push(args[index]);
|
|
17934
|
-
}
|
|
17935
|
-
return scripted ? files : files.slice(1);
|
|
18105
|
+
if (segment.group === "close") {
|
|
18106
|
+
const saved = groups.pop();
|
|
18107
|
+
if (saved)
|
|
18108
|
+
({ dir, stack } = saved);
|
|
18109
|
+
continue;
|
|
17936
18110
|
}
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
17941
|
-
|
|
17942
|
-
|
|
17943
|
-
case "ni":
|
|
17944
|
-
case "remove-item":
|
|
17945
|
-
case "del":
|
|
17946
|
-
case "copy-item":
|
|
17947
|
-
case "move-item": {
|
|
17948
|
-
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
17949
|
-
if (flag >= 0)
|
|
17950
|
-
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
17951
|
-
const files = operands(args);
|
|
17952
|
-
if (name === "move-item")
|
|
17953
|
-
return files.length ? files : null;
|
|
17954
|
-
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
17955
|
-
return file ? [file] : null;
|
|
17956
|
-
}
|
|
17957
|
-
case "git": {
|
|
17958
|
-
const sub = operands(args)[0];
|
|
17959
|
-
if (sub === "apply")
|
|
17960
|
-
return null;
|
|
17961
|
-
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
17962
|
-
return operands(args).slice(1);
|
|
17963
|
-
if (sub === "checkout" && args.includes("--"))
|
|
17964
|
-
return args.slice(args.indexOf("--") + 1);
|
|
17965
|
-
return [];
|
|
17966
|
-
}
|
|
17967
|
-
default:
|
|
17968
|
-
return [];
|
|
17969
|
-
}
|
|
17970
|
-
};
|
|
17971
|
-
var shellWrites = (command) => {
|
|
17972
|
-
const targets = [];
|
|
17973
|
-
let unknown = false;
|
|
17974
|
-
for (const segment of segmentsOf(command)) {
|
|
17975
|
-
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
17976
|
-
const edits = editsOf(segment.words);
|
|
17977
|
-
if (edits === null)
|
|
17978
|
-
unknown = true;
|
|
17979
|
-
else
|
|
17980
|
-
targets.push(...edits);
|
|
17981
|
-
}
|
|
17982
|
-
if (!unknown && targets.length === 0)
|
|
17983
|
-
return { writes: false, targets: null };
|
|
17984
|
-
return { writes: true, targets: unknown ? null : targets };
|
|
17985
|
-
};
|
|
17986
|
-
var cache2 = new Map;
|
|
17987
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path17.resolve(root, file)) ?? "-").join("\x00");
|
|
17988
|
-
var gateFor = (cwd) => {
|
|
17989
|
-
const store = new TaskStore(cwd);
|
|
17990
|
-
const listed = store.listTaskIndex();
|
|
17991
|
-
if (!listed.ok)
|
|
17992
|
-
return null;
|
|
17993
|
-
const key = store.currentKey();
|
|
17994
|
-
if (!key.ok)
|
|
17995
|
-
return null;
|
|
17996
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
17997
|
-
if (!entry)
|
|
17998
|
-
return { blockers: [], root: store.root, plans: new Set };
|
|
17999
|
-
const ledger = ledgerPath(store.root);
|
|
18000
|
-
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18001
|
-
const hit = cache2.get(store.root);
|
|
18002
|
-
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18003
|
-
return hit;
|
|
18004
|
-
const task = store.readTask(entry.id);
|
|
18005
|
-
if (!task.ok)
|
|
18006
|
-
return null;
|
|
18007
|
-
const workspace = store.readWorkspace();
|
|
18008
|
-
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18009
|
-
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18010
|
-
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18011
|
-
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18012
|
-
});
|
|
18013
|
-
const gate = {
|
|
18014
|
-
blockers,
|
|
18015
|
-
root: store.root,
|
|
18016
|
-
plans,
|
|
18017
|
-
key: cacheKey,
|
|
18018
|
-
planKey: planKeyOf(store.root, plans)
|
|
18019
|
-
};
|
|
18020
|
-
cache2.set(store.root, gate);
|
|
18021
|
-
if (cache2.size > 32)
|
|
18022
|
-
cache2.delete(cache2.keys().next().value);
|
|
18023
|
-
return gate;
|
|
18024
|
-
};
|
|
18025
|
-
function writeGate(cwd, paths) {
|
|
18026
|
-
try {
|
|
18027
|
-
const gate = gateFor(cwd);
|
|
18028
|
-
if (!gate || gate.blockers.length === 0)
|
|
18029
|
-
return NONE2;
|
|
18030
|
-
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18031
|
-
return NONE2;
|
|
18032
|
-
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18033
|
-
return {
|
|
18034
|
-
kind: "deny",
|
|
18035
|
-
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18036
|
-
unblock: gate.blockers[0].reason
|
|
18037
|
-
};
|
|
18038
|
-
} catch {
|
|
18039
|
-
return NONE2;
|
|
18040
|
-
}
|
|
18041
|
-
}
|
|
18042
|
-
// packages/workit-core/src/hooks/stop.ts
|
|
18043
|
-
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
18044
|
-
|
|
18045
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18046
|
-
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
18047
|
-
import { createHash as createHash13 } from "node:crypto";
|
|
18048
|
-
import fs15 from "node:fs";
|
|
18049
|
-
import os4 from "node:os";
|
|
18050
|
-
import path19 from "node:path";
|
|
18051
|
-
|
|
18052
|
-
// packages/workit-core/src/hooks/session-marker.ts
|
|
18053
|
-
import { createHash as createHash12 } from "node:crypto";
|
|
18054
|
-
import fs14 from "node:fs";
|
|
18055
|
-
import path18 from "node:path";
|
|
18056
|
-
var sessionMarkerFile = (input, session, store) => {
|
|
18057
|
-
let dir = store;
|
|
18058
|
-
if (dir === undefined) {
|
|
18059
|
-
const location = resolveStore(input.cwd);
|
|
18060
|
-
if (location instanceof Error || !location.shared)
|
|
18061
|
-
return null;
|
|
18062
|
-
dir = location.dir;
|
|
18063
|
-
}
|
|
18064
|
-
const name = createHash12("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
18065
|
-
return path18.join(dir, "hooks", `skills-${name}.json`);
|
|
18066
|
-
};
|
|
18067
|
-
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
18068
|
-
var readSessionMarker = (file) => {
|
|
18069
|
-
try {
|
|
18070
|
-
const value = JSON.parse(fs14.readFileSync(file, "utf8"));
|
|
18071
|
-
return { nudged: list2(value.nudged), loaded: list2(value.loaded), raw: list2(value.raw) };
|
|
18072
|
-
} catch {
|
|
18073
|
-
return { nudged: [], loaded: [], raw: [] };
|
|
18074
|
-
}
|
|
18075
|
-
};
|
|
18076
|
-
var writeSessionMarker = (file, marker) => {
|
|
18077
|
-
try {
|
|
18078
|
-
fs14.mkdirSync(path18.dirname(file), { recursive: true });
|
|
18079
|
-
fs14.writeFileSync(file, JSON.stringify(marker));
|
|
18080
|
-
} catch {}
|
|
18081
|
-
};
|
|
18082
|
-
|
|
18083
|
-
// packages/workit-core/src/hooks/raw-git.ts
|
|
18084
|
-
var NONE3 = { kind: "none" };
|
|
18085
|
-
var SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh"]);
|
|
18086
|
-
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "nice", "&"]);
|
|
18087
|
-
var SCRIPT_FLAG = /^-[a-zA-Z]*c[a-zA-Z]*$/;
|
|
18088
|
-
var CD = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
18089
|
-
var PUSHD = new Set(["pushd", "push-location"]);
|
|
18090
|
-
var POPD = new Set(["popd", "pop-location"]);
|
|
18091
|
-
var GIT_VALUE_OPTIONS = new Set([
|
|
18092
|
-
"-c",
|
|
18093
|
-
"--git-dir",
|
|
18094
|
-
"--work-tree",
|
|
18095
|
-
"--namespace",
|
|
18096
|
-
"--exec-path",
|
|
18097
|
-
"--config-env",
|
|
18098
|
-
"--super-prefix"
|
|
18099
|
-
]);
|
|
18100
|
-
var isAbsolute = (value) => path19.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
18101
|
-
var joinDir = (base, next) => {
|
|
18102
|
-
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
18103
|
-
return;
|
|
18104
|
-
const expanded = next === "~" || next.startsWith("~/") ? path19.join(os4.homedir(), next.slice(1)) : next;
|
|
18105
|
-
return base === null || isAbsolute(expanded) ? expanded : path19.join(base, expanded);
|
|
18106
|
-
};
|
|
18107
|
-
var cdTarget = (words) => {
|
|
18108
|
-
const args = words.slice(1);
|
|
18109
|
-
const flag = args.findIndex((word) => /^-(?:literal)?path$/i.test(word));
|
|
18110
|
-
if (flag >= 0)
|
|
18111
|
-
return args[flag + 1] ?? null;
|
|
18112
|
-
return args.find((word) => word === "-" || !word.startsWith("-")) ?? null;
|
|
18113
|
-
};
|
|
18114
|
-
var stripPrefixes = (input) => {
|
|
18115
|
-
let words = input;
|
|
18116
|
-
for (;; ) {
|
|
18117
|
-
const first = words[0];
|
|
18118
|
-
if (first === undefined)
|
|
18119
|
-
return words;
|
|
18120
|
-
if (/^\w+=/.test(first))
|
|
18121
|
-
words = words.slice(1);
|
|
18122
|
-
else if (PREFIXES3.has(first)) {
|
|
18123
|
-
words = words.slice(1);
|
|
18124
|
-
while (words[0]?.startsWith("-"))
|
|
18125
|
-
words = words.slice(words[0] === "-u" ? 2 : 1);
|
|
18126
|
-
} else if (first === "timeout") {
|
|
18127
|
-
words = words.slice(1);
|
|
18128
|
-
while (words[0]?.startsWith("-"))
|
|
18129
|
-
words = words.slice(1);
|
|
18130
|
-
words = words.slice(1);
|
|
18131
|
-
} else
|
|
18132
|
-
return words;
|
|
18133
|
-
}
|
|
18134
|
-
};
|
|
18135
|
-
var commandName = (word) => (word?.split(/[\\/]/).at(-1) ?? "").toLowerCase().replace(/\.exe$/, "");
|
|
18136
|
-
function rawInvocations(command, dialect = "posix", depth = 0) {
|
|
18137
|
-
const found = [];
|
|
18138
|
-
let dir = null;
|
|
18139
|
-
const groups = [];
|
|
18140
|
-
let stack = [];
|
|
18141
|
-
for (const segment of segmentsOf(command, dialect)) {
|
|
18142
|
-
if (segment.group === "open") {
|
|
18143
|
-
groups.push({ dir, stack: [...stack] });
|
|
18144
|
-
continue;
|
|
18145
|
-
}
|
|
18146
|
-
if (segment.group === "close") {
|
|
18147
|
-
const saved = groups.pop();
|
|
18148
|
-
if (saved)
|
|
18149
|
-
({ dir, stack } = saved);
|
|
18150
|
-
continue;
|
|
18151
|
-
}
|
|
18152
|
-
const words = stripPrefixes(segment.words);
|
|
18153
|
-
const name = commandName(words[0]);
|
|
18154
|
-
if (CD.has(name)) {
|
|
18155
|
-
const target = cdTarget(words);
|
|
18156
|
-
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
18157
|
-
continue;
|
|
18111
|
+
const words = stripPrefixes(segment.words);
|
|
18112
|
+
const name = commandName(words[0]);
|
|
18113
|
+
if (CD.has(name)) {
|
|
18114
|
+
const target = cdTarget(words);
|
|
18115
|
+
dir = target === null ? os4.homedir() : joinDir(dir, target);
|
|
18116
|
+
continue;
|
|
18158
18117
|
}
|
|
18159
18118
|
if (PUSHD.has(name)) {
|
|
18160
18119
|
const target = cdTarget(words);
|
|
@@ -18234,6 +18193,10 @@ var pushAction = (args) => {
|
|
|
18234
18193
|
let force = false;
|
|
18235
18194
|
let lease = false;
|
|
18236
18195
|
let wholesale = false;
|
|
18196
|
+
let allTags = false;
|
|
18197
|
+
let deletes = false;
|
|
18198
|
+
let allBranches = false;
|
|
18199
|
+
let mirror = false;
|
|
18237
18200
|
let remote = null;
|
|
18238
18201
|
const positionals = [];
|
|
18239
18202
|
for (let index = 1;index < args.length; index++) {
|
|
@@ -18254,14 +18217,22 @@ var pushAction = (args) => {
|
|
|
18254
18217
|
force = true;
|
|
18255
18218
|
else if (arg.startsWith("--force-with-lease") || arg === "--force-if-includes")
|
|
18256
18219
|
lease = true;
|
|
18257
|
-
else if (
|
|
18258
|
-
|
|
18220
|
+
else if (arg === "--delete" || arg === "-d")
|
|
18221
|
+
deletes = true;
|
|
18222
|
+
else if (arg === "--tags")
|
|
18223
|
+
allTags = wholesale = true;
|
|
18224
|
+
else if (arg === "--all" || arg === "--branches")
|
|
18225
|
+
allBranches = wholesale = true;
|
|
18226
|
+
else if (arg === "--mirror")
|
|
18227
|
+
mirror = wholesale = true;
|
|
18259
18228
|
else if (!arg.startsWith("-"))
|
|
18260
18229
|
positionals.push(arg);
|
|
18261
18230
|
}
|
|
18262
18231
|
const specs = remote === null ? positionals.slice(1) : positionals;
|
|
18263
18232
|
remote ??= positionals[0] ?? null;
|
|
18264
18233
|
const targets = [];
|
|
18234
|
+
const tags = [];
|
|
18235
|
+
const deleted = [];
|
|
18265
18236
|
for (const raw of specs) {
|
|
18266
18237
|
if (raw.startsWith("+"))
|
|
18267
18238
|
force = true;
|
|
@@ -18270,16 +18241,29 @@ var pushAction = (args) => {
|
|
|
18270
18241
|
const destination = colon >= 0 ? spec.slice(colon + 1) : spec;
|
|
18271
18242
|
if (!destination)
|
|
18272
18243
|
continue;
|
|
18273
|
-
|
|
18274
|
-
|
|
18275
|
-
|
|
18276
|
-
|
|
18277
|
-
|
|
18278
|
-
|
|
18244
|
+
const name = destination === "HEAD" ? null : destination.startsWith("refs/heads/") ? destination.slice(11) : destination.startsWith("refs/") ? undefined : destination;
|
|
18245
|
+
if (destination.startsWith("refs/tags/"))
|
|
18246
|
+
tags.push(destination.slice(10));
|
|
18247
|
+
if (name === undefined)
|
|
18248
|
+
continue;
|
|
18249
|
+
targets.push(name);
|
|
18250
|
+
if (colon === 0 && name !== null)
|
|
18251
|
+
deleted.push(name);
|
|
18279
18252
|
}
|
|
18280
18253
|
if (specs.length === 0 && !wholesale)
|
|
18281
18254
|
targets.push(null);
|
|
18282
|
-
return {
|
|
18255
|
+
return {
|
|
18256
|
+
kind: "push",
|
|
18257
|
+
targets,
|
|
18258
|
+
blindForce: force && !lease,
|
|
18259
|
+
remote,
|
|
18260
|
+
...tags.length ? { tags } : {},
|
|
18261
|
+
...allTags ? { allTags: true } : {},
|
|
18262
|
+
...deletes ? { deletes: true } : {},
|
|
18263
|
+
...deleted.length ? { deleted } : {},
|
|
18264
|
+
...allBranches ? { allBranches: true } : {},
|
|
18265
|
+
...mirror ? { mirror: true } : {}
|
|
18266
|
+
};
|
|
18283
18267
|
};
|
|
18284
18268
|
var VALUE_OPTIONS = {
|
|
18285
18269
|
git: new Set([
|
|
@@ -18355,7 +18339,7 @@ var repoAt = (dir) => {
|
|
|
18355
18339
|
return { location, dir, branch: resolveTaskKey(dir, location).branch };
|
|
18356
18340
|
};
|
|
18357
18341
|
var isWorkitWorkspace = (repo) => {
|
|
18358
|
-
if (
|
|
18342
|
+
if (fs16.existsSync(repo.location.dir))
|
|
18359
18343
|
return true;
|
|
18360
18344
|
try {
|
|
18361
18345
|
return resolveRuntimeWorkspaceVcs(repo.location.top ?? repo.dir) !== null;
|
|
@@ -18386,7 +18370,7 @@ var headSha2 = (repo) => {
|
|
|
18386
18370
|
if (!git || git.reftable)
|
|
18387
18371
|
return null;
|
|
18388
18372
|
try {
|
|
18389
|
-
const head =
|
|
18373
|
+
const head = fs16.readFileSync(path19.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18390
18374
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18391
18375
|
return head;
|
|
18392
18376
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
@@ -18394,20 +18378,72 @@ var headSha2 = (repo) => {
|
|
|
18394
18378
|
return null;
|
|
18395
18379
|
const common = path19.dirname(repo.location.dir);
|
|
18396
18380
|
try {
|
|
18397
|
-
return
|
|
18381
|
+
return fs16.readFileSync(path19.join(common, ref), "utf8").trim() || null;
|
|
18398
18382
|
} catch {}
|
|
18399
|
-
const packed =
|
|
18383
|
+
const packed = fs16.readFileSync(path19.join(common, "packed-refs"), "utf8");
|
|
18400
18384
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18401
18385
|
} catch {
|
|
18402
18386
|
return null;
|
|
18403
18387
|
}
|
|
18404
18388
|
};
|
|
18405
18389
|
var quote = (text) => `\`${text}\``;
|
|
18406
|
-
var refusal = (action, branch) => {
|
|
18390
|
+
var refusal = (action, branch, dir) => {
|
|
18407
18391
|
if (action.kind === "merge")
|
|
18408
18392
|
return `workit: raw ${quote(action.forge === "gh" ? "gh pr merge" : "glab mr merge")} bypasses the merge grant and the verdict gate. Run ${quote("workit pr merge")} instead (it checks both; ${quote("workit help pr")}).`;
|
|
18393
|
+
const plainPush = action.kind === "push" && !action.blindForce && !action.deletes;
|
|
18394
|
+
if (dir && branch && plainPush && releaseBranch(dir, branch))
|
|
18395
|
+
return `workit: raw ${quote("git push")} onto ${branch}, a protected release branch, bypasses workit's protected-branch check. To finish a release or hotfix, push it with ${quote(`workit git push --release ${branch} [--release v<version>]`)}: it pushes ${branch} (fast-forward only) and the release tag, verifies the remote and records it, under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}). Feature work goes through ${quote("workit git push")} and ${quote("workit pr merge")}.`;
|
|
18409
18396
|
return `workit: raw ${quote("git push")}${action.kind === "push" && action.blindForce ? " --force" : ""} onto ${branch}, a protected branch or the default target, bypasses workit's protected-branch check. Push your feature branch with ${quote("workit git push")} (${quote("workit git push --force-with-lease")} to rewrite it) and land it with ${quote("workit pr merge")}.`;
|
|
18410
18397
|
};
|
|
18398
|
+
var refFacts = (repo, remote) => {
|
|
18399
|
+
const facts = { heads: new Set, tags: new Set, remoteHeads: new Set };
|
|
18400
|
+
const listed = spawnSync11("git", ["for-each-ref", "--format=%(refname)", "refs/heads", "refs/tags", `refs/remotes/${remote}`], { cwd: repo.dir, encoding: "utf8", timeout: 5000 });
|
|
18401
|
+
const remotePrefix = `refs/remotes/${remote}/`;
|
|
18402
|
+
for (const ref of (listed.stdout ?? "").split(`
|
|
18403
|
+
`)) {
|
|
18404
|
+
if (ref.startsWith("refs/heads/"))
|
|
18405
|
+
facts.heads.add(ref.slice(11));
|
|
18406
|
+
else if (ref.startsWith("refs/tags/"))
|
|
18407
|
+
facts.tags.add(ref.slice(10));
|
|
18408
|
+
else if (ref.startsWith(remotePrefix))
|
|
18409
|
+
facts.remoteHeads.add(ref.slice(remotePrefix.length));
|
|
18410
|
+
}
|
|
18411
|
+
return facts;
|
|
18412
|
+
};
|
|
18413
|
+
var isTag = (facts, name) => facts.tags.has(name) && !facts.heads.has(name);
|
|
18414
|
+
var tagRefusal = (action, facts) => {
|
|
18415
|
+
const tags = [
|
|
18416
|
+
...action.tags ?? [],
|
|
18417
|
+
...action.targets.filter((name) => name !== null && isTag(facts, name))
|
|
18418
|
+
];
|
|
18419
|
+
if (!tags.length && !action.allTags)
|
|
18420
|
+
return null;
|
|
18421
|
+
const named = tags.length ? tags.join(" --release ") : "v<version>";
|
|
18422
|
+
return `workit: raw ${quote(`git push${action.allTags ? " --tags" : ""}`)} of ${tags.length ? `tag ${tags.join(", ")}` : "tags"} bypasses the release grant. Push a release tag with ${quote(`workit git push --release ${named}`)} (v<version> tags whose commit the production branch already has; a tag never moves), under the ${quote("release")} grant (if refused, ask the user to run ${quote("workit grant set <workspace> release=true")}).`;
|
|
18423
|
+
};
|
|
18424
|
+
var remoteHas = (repo, remote, branch) => {
|
|
18425
|
+
const listed = spawnSync11("git", ["ls-remote", "--heads", remote, `refs/heads/${branch}`], {
|
|
18426
|
+
cwd: repo.dir,
|
|
18427
|
+
encoding: "utf8",
|
|
18428
|
+
timeout: 3000,
|
|
18429
|
+
env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
|
|
18430
|
+
});
|
|
18431
|
+
return listed.status !== 0 || (listed.stdout ?? "").trim() !== "";
|
|
18432
|
+
};
|
|
18433
|
+
var offPolicyPush = (action, repo, facts, remote) => {
|
|
18434
|
+
const names = action.allBranches ? [...facts.heads] : action.targets.map((target) => target ?? repo.branch);
|
|
18435
|
+
for (const branch of names) {
|
|
18436
|
+
if (!branch || action.deleted?.includes(branch) || isTag(facts, branch))
|
|
18437
|
+
continue;
|
|
18438
|
+
if (facts.remoteHeads.has(branch))
|
|
18439
|
+
continue;
|
|
18440
|
+
const check = validateBranchNameFor(repo.dir, branch);
|
|
18441
|
+
if (check.ok || check.rule !== "allowed_pattern" || remoteHas(repo, remote, branch))
|
|
18442
|
+
continue;
|
|
18443
|
+
return `workit: raw ${quote("git push")} onto ${branch}: ${check.error}`;
|
|
18444
|
+
}
|
|
18445
|
+
return null;
|
|
18446
|
+
};
|
|
18411
18447
|
var nudgeFor = (action, host, session) => {
|
|
18412
18448
|
switch (action.kind) {
|
|
18413
18449
|
case "commit": {
|
|
@@ -18470,7 +18506,7 @@ var unseenNudges = (input, nudges, store) => {
|
|
|
18470
18506
|
function rawGitPre(input, command, options = {}) {
|
|
18471
18507
|
try {
|
|
18472
18508
|
if (!mentionsRawTool(command))
|
|
18473
|
-
return
|
|
18509
|
+
return NONE2;
|
|
18474
18510
|
const dialect = dialectOf(input);
|
|
18475
18511
|
const nudges = new Map;
|
|
18476
18512
|
let store = null;
|
|
@@ -18478,32 +18514,43 @@ function rawGitPre(input, command, options = {}) {
|
|
|
18478
18514
|
store ??= repo.location.dir;
|
|
18479
18515
|
if (action.kind === "merge")
|
|
18480
18516
|
return deny(refusal(action, null));
|
|
18481
|
-
if (action.kind === "push")
|
|
18517
|
+
if (action.kind === "push" && toPushRemote(repo, action.remote)) {
|
|
18518
|
+
if (action.mirror)
|
|
18519
|
+
return deny(`workit: raw ${quote("git push --mirror")} rewrites and deletes every remote ref, protected branches and tags included. Push one branch with ${quote("workit git push")}.`);
|
|
18482
18520
|
for (const target of action.targets) {
|
|
18483
18521
|
const branch = target ?? repo.branch;
|
|
18484
|
-
if (branch && guardedBranch(repo.dir, branch)
|
|
18485
|
-
return deny(refusal(action, branch));
|
|
18522
|
+
if (branch && guardedBranch(repo.dir, branch))
|
|
18523
|
+
return deny(refusal(action, branch, repo.dir));
|
|
18486
18524
|
}
|
|
18525
|
+
const remote = action.remote ?? pushRemoteName(repo.dir, repo.branch) ?? "origin";
|
|
18526
|
+
const facts = refFacts(repo, remote);
|
|
18527
|
+
const tagDeny = tagRefusal(action, facts);
|
|
18528
|
+
if (tagDeny)
|
|
18529
|
+
return deny(tagDeny);
|
|
18530
|
+
const nameDeny = action.deletes ? null : offPolicyPush(action, repo, facts, remote);
|
|
18531
|
+
if (nameDeny)
|
|
18532
|
+
return deny(nameDeny);
|
|
18533
|
+
}
|
|
18487
18534
|
const nudge = nudgeFor(action, input.host, input.session.id);
|
|
18488
18535
|
if (nudge && !nudges.has(nudgeKind(action)))
|
|
18489
18536
|
nudges.set(nudgeKind(action), nudge);
|
|
18490
18537
|
}
|
|
18491
18538
|
if (options.nudge === false || !store || nudges.size === 0)
|
|
18492
|
-
return
|
|
18539
|
+
return NONE2;
|
|
18493
18540
|
if (workitInvocations(command, dialect).length > 0)
|
|
18494
|
-
return
|
|
18541
|
+
return NONE2;
|
|
18495
18542
|
const shown = unseenNudges(input, nudges, store);
|
|
18496
18543
|
return shown.length ? { kind: "context", text: shown.join(`
|
|
18497
|
-
`) } :
|
|
18544
|
+
`) } : NONE2;
|
|
18498
18545
|
} catch {
|
|
18499
|
-
return
|
|
18546
|
+
return NONE2;
|
|
18500
18547
|
}
|
|
18501
18548
|
}
|
|
18502
18549
|
var deny = (reason) => ({ kind: "deny", reason, unblock: null });
|
|
18503
18550
|
var NEXT_COMMAND = "next";
|
|
18504
18551
|
var MARKER_TTL_SECONDS = 3600;
|
|
18505
|
-
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${
|
|
18506
|
-
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${
|
|
18552
|
+
var markerFile = (repo, session, key) => path19.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18553
|
+
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
18507
18554
|
function noteRawCommit(input, command, key) {
|
|
18508
18555
|
try {
|
|
18509
18556
|
if (!input.session.id || !/\bcommit\b/.test(command))
|
|
@@ -18513,8 +18560,8 @@ function noteRawCommit(input, command, key) {
|
|
|
18513
18560
|
if (action.kind !== "commit")
|
|
18514
18561
|
continue;
|
|
18515
18562
|
const file = markerFile(repo, input.session.id, key);
|
|
18516
|
-
|
|
18517
|
-
|
|
18563
|
+
fs16.mkdirSync(path19.dirname(file), { recursive: true });
|
|
18564
|
+
fs16.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18518
18565
|
`);
|
|
18519
18566
|
}
|
|
18520
18567
|
} catch {}
|
|
@@ -18537,11 +18584,11 @@ var headCommit = (dir) => {
|
|
|
18537
18584
|
var settle2 = (input, repo, file) => {
|
|
18538
18585
|
let marker;
|
|
18539
18586
|
try {
|
|
18540
|
-
marker = JSON.parse(
|
|
18587
|
+
marker = JSON.parse(fs16.readFileSync(file, "utf8"));
|
|
18541
18588
|
} catch {
|
|
18542
18589
|
return;
|
|
18543
18590
|
}
|
|
18544
|
-
|
|
18591
|
+
fs16.rmSync(file, { force: true });
|
|
18545
18592
|
const session = input.session.id;
|
|
18546
18593
|
const now = Date.now() / 1000;
|
|
18547
18594
|
if (typeof marker.at !== "number" || now - marker.at > MARKER_TTL_SECONDS)
|
|
@@ -18590,12 +18637,297 @@ function settlePendingCommit(input) {
|
|
|
18590
18637
|
if (!repo)
|
|
18591
18638
|
return;
|
|
18592
18639
|
const file = markerFile(repo, input.session.id, NEXT_COMMAND);
|
|
18593
|
-
if (
|
|
18640
|
+
if (fs16.existsSync(file))
|
|
18594
18641
|
settle2(input, repo, file);
|
|
18595
18642
|
} catch {}
|
|
18596
18643
|
}
|
|
18597
18644
|
|
|
18645
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
18646
|
+
var UNKNOWABLE = /[$`*?[\]{}~]/u;
|
|
18647
|
+
var SWITCH_CREATE = new Set(["-c", "--create", "-C", "--force-create", "--orphan"]);
|
|
18648
|
+
var CHECKOUT_CREATE = new Set(["-b", "-B", "--orphan"]);
|
|
18649
|
+
var WORKTREE_CREATE = new Set(["-b", "-B"]);
|
|
18650
|
+
var BRANCH_NO_CREATE = new Set(["-d", "-D", "--delete", "-l", "--list", "-a", "--all", "-r", "--remotes", "--show-current"].concat(["-v", "-vv", "--verbose", "--contains", "--no-contains", "--merged", "--no-merged"]).concat(["--points-at", "--sort", "--format", "--column", "--no-column", "-u"]).concat(["--set-upstream-to", "--unset-upstream", "--edit-description", "-i", "--ignore-case"]));
|
|
18651
|
+
var BRANCH_RENAME = new Set(["-m", "-M", "--move", "-c", "-C", "--copy"]);
|
|
18652
|
+
var flagValue = (args, flags) => {
|
|
18653
|
+
for (let index = 1;index < args.length; index++) {
|
|
18654
|
+
const arg = args[index];
|
|
18655
|
+
if (arg === "--")
|
|
18656
|
+
return null;
|
|
18657
|
+
if (flags.has(arg))
|
|
18658
|
+
return args[index + 1] ?? null;
|
|
18659
|
+
const eq = arg.indexOf("=");
|
|
18660
|
+
if (arg.startsWith("--") && eq > 0 && flags.has(arg.slice(0, eq)))
|
|
18661
|
+
return arg.slice(eq + 1);
|
|
18662
|
+
if (!arg.startsWith("--") && arg.length > 2 && flags.has(arg.slice(0, 2)))
|
|
18663
|
+
return arg.slice(2);
|
|
18664
|
+
}
|
|
18665
|
+
return null;
|
|
18666
|
+
};
|
|
18667
|
+
var WORKTREE_VALUES = new Set(["--reason"]);
|
|
18668
|
+
var worktreeTarget = (args) => {
|
|
18669
|
+
const named = flagValue(args, WORKTREE_CREATE);
|
|
18670
|
+
if (named !== null)
|
|
18671
|
+
return named;
|
|
18672
|
+
const positionals = [];
|
|
18673
|
+
for (let index = 1;index < args.length; index++) {
|
|
18674
|
+
const arg = args[index];
|
|
18675
|
+
if (arg === "--detach" || arg === "-d")
|
|
18676
|
+
return null;
|
|
18677
|
+
if (WORKTREE_VALUES.has(arg))
|
|
18678
|
+
index++;
|
|
18679
|
+
else if (!arg.startsWith("-"))
|
|
18680
|
+
positionals.push(arg);
|
|
18681
|
+
}
|
|
18682
|
+
if (positionals.length !== 1)
|
|
18683
|
+
return null;
|
|
18684
|
+
const base = positionals[0].split(/[\\/]/u).findLast(Boolean) ?? "";
|
|
18685
|
+
return base && base !== "." && base !== ".." ? base : null;
|
|
18686
|
+
};
|
|
18687
|
+
var branchCommandTarget = (args) => {
|
|
18688
|
+
let rename = false;
|
|
18689
|
+
const positionals = [];
|
|
18690
|
+
for (let index = 1;index < args.length; index++) {
|
|
18691
|
+
const arg = args[index];
|
|
18692
|
+
if (arg === "--") {
|
|
18693
|
+
positionals.push(...args.slice(index + 1));
|
|
18694
|
+
break;
|
|
18695
|
+
}
|
|
18696
|
+
const flag = arg.split("=", 1)[0];
|
|
18697
|
+
if (BRANCH_NO_CREATE.has(flag))
|
|
18698
|
+
return null;
|
|
18699
|
+
if (BRANCH_RENAME.has(flag))
|
|
18700
|
+
rename = true;
|
|
18701
|
+
else if (!arg.startsWith("-"))
|
|
18702
|
+
positionals.push(arg);
|
|
18703
|
+
}
|
|
18704
|
+
if (positionals.length < 1 || positionals.length > 2)
|
|
18705
|
+
return null;
|
|
18706
|
+
return rename ? positionals.at(-1) ?? null : positionals[0];
|
|
18707
|
+
};
|
|
18708
|
+
var gitBranchCreation = (args) => {
|
|
18709
|
+
const [sub, second] = args;
|
|
18710
|
+
const target = sub === "switch" ? flagValue(args, SWITCH_CREATE) : sub === "checkout" ? flagValue(args, CHECKOUT_CREATE) : sub === "branch" ? branchCommandTarget(args) : sub === "worktree" && second === "add" ? worktreeTarget(args.slice(1)) : null;
|
|
18711
|
+
return target && !target.startsWith("-") && !UNKNOWABLE.test(target) ? target : null;
|
|
18712
|
+
};
|
|
18713
|
+
var shellBranchTargets = (command, dialect = "posix") => {
|
|
18714
|
+
if (!/\bgit\b/u.test(command))
|
|
18715
|
+
return [];
|
|
18716
|
+
const found = [];
|
|
18717
|
+
for (const invocation of rawInvocations(command, dialect)) {
|
|
18718
|
+
if (invocation.tool !== "git")
|
|
18719
|
+
continue;
|
|
18720
|
+
const branch = gitBranchCreation(invocation.args);
|
|
18721
|
+
if (branch)
|
|
18722
|
+
found.push({ branch, dir: invocation.dir });
|
|
18723
|
+
}
|
|
18724
|
+
return found;
|
|
18725
|
+
};
|
|
18726
|
+
var localBranchExists = (dir, branch) => spawnSync12("git", ["show-ref", "--verify", "--quiet", `refs/heads/${branch}`], {
|
|
18727
|
+
cwd: dir,
|
|
18728
|
+
stdio: "ignore",
|
|
18729
|
+
timeout: 5000
|
|
18730
|
+
}).status === 0;
|
|
18731
|
+
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18732
|
+
let judged = false;
|
|
18733
|
+
for (const target of shellBranchTargets(command, dialect)) {
|
|
18734
|
+
if (target.dir === undefined)
|
|
18735
|
+
continue;
|
|
18736
|
+
const dir = target.dir === null ? root : path20.resolve(root, target.dir);
|
|
18737
|
+
if (target.dir !== null && (!existsSync6(dir) || !inWorkitWorkspace(dir)))
|
|
18738
|
+
continue;
|
|
18739
|
+
if (localBranchExists(dir, target.branch))
|
|
18740
|
+
continue;
|
|
18741
|
+
judged = true;
|
|
18742
|
+
const check = validateBranchNameFor(dir, target.branch);
|
|
18743
|
+
if (!check.ok)
|
|
18744
|
+
return check;
|
|
18745
|
+
}
|
|
18746
|
+
return judged ? { ok: true } : null;
|
|
18747
|
+
};
|
|
18748
|
+
|
|
18749
|
+
// packages/workit-core/src/hooks/policy.ts
|
|
18750
|
+
var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
18751
|
+
const policy = shellBranchPolicyViolation(cwd, command, dialect);
|
|
18752
|
+
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18753
|
+
};
|
|
18754
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
18755
|
+
import { realpathSync as realpathSync5 } from "node:fs";
|
|
18756
|
+
import path21 from "node:path";
|
|
18757
|
+
var NONE3 = { kind: "none" };
|
|
18758
|
+
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
18759
|
+
var canonical2 = (file) => {
|
|
18760
|
+
let rest = "";
|
|
18761
|
+
for (let dir = path21.resolve(file);; dir = path21.dirname(dir)) {
|
|
18762
|
+
try {
|
|
18763
|
+
return path21.join(realpathSync5(dir), rest);
|
|
18764
|
+
} catch {
|
|
18765
|
+
if (path21.dirname(dir) === dir)
|
|
18766
|
+
return path21.resolve(file);
|
|
18767
|
+
rest = rest ? path21.join(path21.basename(dir), rest) : path21.basename(dir);
|
|
18768
|
+
}
|
|
18769
|
+
}
|
|
18770
|
+
};
|
|
18771
|
+
var exempt = (cwd, root, file, plans) => {
|
|
18772
|
+
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
18773
|
+
const relative = path21.relative(fold(canonical2(root)), fold(canonical2(path21.resolve(cwd, file))));
|
|
18774
|
+
if (relative.startsWith("..") || path21.isAbsolute(relative))
|
|
18775
|
+
return true;
|
|
18776
|
+
const posix = relative.split(path21.sep).join("/");
|
|
18777
|
+
const segments = posix.split("/");
|
|
18778
|
+
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
18779
|
+
};
|
|
18780
|
+
var PREFIXES3 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
18781
|
+
var operands = (words) => words.filter((item) => !item.startsWith("-"));
|
|
18782
|
+
var editsOf = (input) => {
|
|
18783
|
+
let words = input;
|
|
18784
|
+
while (words.length && (PREFIXES3.has(words[0]) || /^\w+=/.test(words[0])))
|
|
18785
|
+
words = words.slice(1);
|
|
18786
|
+
const [verb, ...args] = words;
|
|
18787
|
+
const name = (verb?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
18788
|
+
if (!name || name === "workit" || name === "npx" && args.some((item) => /workit/.test(item)))
|
|
18789
|
+
return [];
|
|
18790
|
+
switch (name) {
|
|
18791
|
+
case "touch":
|
|
18792
|
+
case "mkdir":
|
|
18793
|
+
case "rm":
|
|
18794
|
+
case "rmdir":
|
|
18795
|
+
case "tee":
|
|
18796
|
+
case "mv":
|
|
18797
|
+
case "truncate":
|
|
18798
|
+
return operands(args);
|
|
18799
|
+
case "cp":
|
|
18800
|
+
case "ln":
|
|
18801
|
+
case "install": {
|
|
18802
|
+
const target = args.indexOf("-t");
|
|
18803
|
+
if (target >= 0)
|
|
18804
|
+
return args[target + 1] ? [args[target + 1]] : null;
|
|
18805
|
+
const last = operands(args).at(-1);
|
|
18806
|
+
return last ? [last] : null;
|
|
18807
|
+
}
|
|
18808
|
+
case "dd":
|
|
18809
|
+
return args.filter((item) => item.startsWith("of=")).map((item) => item.slice(3));
|
|
18810
|
+
case "sed":
|
|
18811
|
+
case "perl": {
|
|
18812
|
+
if (!args.some((item) => /^-[a-zA-Z]*i|^--in-place/.test(item)))
|
|
18813
|
+
return [];
|
|
18814
|
+
const scripted = args.some((item) => item === "-e" || item === "-f");
|
|
18815
|
+
const files = [];
|
|
18816
|
+
for (let index = 0;index < args.length; index++) {
|
|
18817
|
+
if (args[index] === "-e" || args[index] === "-f")
|
|
18818
|
+
index++;
|
|
18819
|
+
else if (!args[index].startsWith("-"))
|
|
18820
|
+
files.push(args[index]);
|
|
18821
|
+
}
|
|
18822
|
+
return scripted ? files : files.slice(1);
|
|
18823
|
+
}
|
|
18824
|
+
case "patch":
|
|
18825
|
+
return null;
|
|
18826
|
+
case "set-content":
|
|
18827
|
+
case "add-content":
|
|
18828
|
+
case "out-file":
|
|
18829
|
+
case "new-item":
|
|
18830
|
+
case "ni":
|
|
18831
|
+
case "remove-item":
|
|
18832
|
+
case "del":
|
|
18833
|
+
case "copy-item":
|
|
18834
|
+
case "move-item": {
|
|
18835
|
+
const flag = args.findIndex((item) => /^-(?:path|filepath|literalpath|destination)$/i.test(item));
|
|
18836
|
+
if (flag >= 0)
|
|
18837
|
+
return args[flag + 1] ? [args[flag + 1]] : null;
|
|
18838
|
+
const files = operands(args);
|
|
18839
|
+
if (name === "move-item")
|
|
18840
|
+
return files.length ? files : null;
|
|
18841
|
+
const file = name === "copy-item" ? files.at(-1) : files[0];
|
|
18842
|
+
return file ? [file] : null;
|
|
18843
|
+
}
|
|
18844
|
+
case "git": {
|
|
18845
|
+
const sub = operands(args)[0];
|
|
18846
|
+
if (sub === "apply")
|
|
18847
|
+
return null;
|
|
18848
|
+
if (sub === "restore" || sub === "mv" || sub === "rm")
|
|
18849
|
+
return operands(args).slice(1);
|
|
18850
|
+
if (sub === "checkout" && args.includes("--"))
|
|
18851
|
+
return args.slice(args.indexOf("--") + 1);
|
|
18852
|
+
return [];
|
|
18853
|
+
}
|
|
18854
|
+
default:
|
|
18855
|
+
return [];
|
|
18856
|
+
}
|
|
18857
|
+
};
|
|
18858
|
+
var shellWrites = (command) => {
|
|
18859
|
+
const targets = [];
|
|
18860
|
+
let unknown = false;
|
|
18861
|
+
for (const segment of segmentsOf(command)) {
|
|
18862
|
+
targets.push(...segment.redirects.filter((item) => !/^\/dev\/(?:null|stdout|stderr|tty)$/.test(item)));
|
|
18863
|
+
const edits = editsOf(segment.words);
|
|
18864
|
+
if (edits === null)
|
|
18865
|
+
unknown = true;
|
|
18866
|
+
else
|
|
18867
|
+
targets.push(...edits);
|
|
18868
|
+
}
|
|
18869
|
+
if (!unknown && targets.length === 0)
|
|
18870
|
+
return { writes: false, targets: null };
|
|
18871
|
+
return { writes: true, targets: unknown ? null : targets };
|
|
18872
|
+
};
|
|
18873
|
+
var cache2 = new Map;
|
|
18874
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path21.resolve(root, file)) ?? "-").join("\x00");
|
|
18875
|
+
var gateFor = (cwd) => {
|
|
18876
|
+
const store = new TaskStore(cwd);
|
|
18877
|
+
const listed = store.listTaskIndex();
|
|
18878
|
+
if (!listed.ok)
|
|
18879
|
+
return null;
|
|
18880
|
+
const key = store.currentKey();
|
|
18881
|
+
if (!key.ok)
|
|
18882
|
+
return null;
|
|
18883
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
18884
|
+
if (!entry)
|
|
18885
|
+
return { blockers: [], root: store.root, plans: new Set };
|
|
18886
|
+
const ledger = ledgerPath(store.root);
|
|
18887
|
+
const cacheKey = `${entry.id}\x00${entry.revision}\x00${ledger.ok && fileSignature(ledger.value) || "-"}`;
|
|
18888
|
+
const hit = cache2.get(store.root);
|
|
18889
|
+
if (hit && hit.key === cacheKey && hit.planKey === planKeyOf(store.root, hit.plans))
|
|
18890
|
+
return hit;
|
|
18891
|
+
const task = store.readTask(entry.id);
|
|
18892
|
+
if (!task.ok)
|
|
18893
|
+
return null;
|
|
18894
|
+
const workspace = store.readWorkspace();
|
|
18895
|
+
const plans = new Set((latestJudgment(task.data)?.refs ?? []).flatMap((ref) => ref.kind === "file" ? [ref.path] : []));
|
|
18896
|
+
const blockers = writeBlockers(task.data, store.root).filter((blocker) => {
|
|
18897
|
+
const requirement = task.data.policy?.requirements.find((item) => item.ruleId === blocker.ruleId && item.before === "write");
|
|
18898
|
+
return !(requirement?.acceptanceAllowed && workspace.ok && workspace.data && applicableDecision(task.data, workspace.data, requirement, store.root).length > 0);
|
|
18899
|
+
});
|
|
18900
|
+
const gate = {
|
|
18901
|
+
blockers,
|
|
18902
|
+
root: store.root,
|
|
18903
|
+
plans,
|
|
18904
|
+
key: cacheKey,
|
|
18905
|
+
planKey: planKeyOf(store.root, plans)
|
|
18906
|
+
};
|
|
18907
|
+
cache2.set(store.root, gate);
|
|
18908
|
+
if (cache2.size > 32)
|
|
18909
|
+
cache2.delete(cache2.keys().next().value);
|
|
18910
|
+
return gate;
|
|
18911
|
+
};
|
|
18912
|
+
function writeGate(cwd, paths) {
|
|
18913
|
+
try {
|
|
18914
|
+
const gate = gateFor(cwd);
|
|
18915
|
+
if (!gate || gate.blockers.length === 0)
|
|
18916
|
+
return NONE3;
|
|
18917
|
+
if (paths?.length && paths.every((file) => exempt(cwd, gate.root, file, gate.plans)))
|
|
18918
|
+
return NONE3;
|
|
18919
|
+
const reasons = gate.blockers.map((item) => `${item.ruleId}: ${item.reason}`).join("; ");
|
|
18920
|
+
return {
|
|
18921
|
+
kind: "deny",
|
|
18922
|
+
reason: `workit before-write gate: ${reasons}. Markdown, docs/, plans/ and files outside the checkout stay writable.`,
|
|
18923
|
+
unblock: gate.blockers[0].reason
|
|
18924
|
+
};
|
|
18925
|
+
} catch {
|
|
18926
|
+
return NONE3;
|
|
18927
|
+
}
|
|
18928
|
+
}
|
|
18598
18929
|
// packages/workit-core/src/hooks/stop.ts
|
|
18930
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
18599
18931
|
var NONE4 = { kind: "none" };
|
|
18600
18932
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
18601
18933
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -18616,7 +18948,7 @@ var asksUser = (message) => {
|
|
|
18616
18948
|
};
|
|
18617
18949
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
18618
18950
|
var gitList = (cwd, args) => {
|
|
18619
|
-
const run =
|
|
18951
|
+
const run = spawnSync13("git", args, {
|
|
18620
18952
|
cwd,
|
|
18621
18953
|
encoding: "utf8",
|
|
18622
18954
|
timeout: 3000,
|
|
@@ -18979,13 +19311,13 @@ ${line}` };
|
|
|
18979
19311
|
return decision;
|
|
18980
19312
|
};
|
|
18981
19313
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
18982
|
-
import { existsSync as
|
|
18983
|
-
import
|
|
19314
|
+
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
19315
|
+
import path22 from "node:path";
|
|
18984
19316
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
18985
19317
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
18986
19318
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
18987
19319
|
var existingDirectory = (value) => {
|
|
18988
|
-
if (!nonEmpty2(value) || !
|
|
19320
|
+
if (!nonEmpty2(value) || !path22.isAbsolute(value) || !existsSync7(value))
|
|
18989
19321
|
return null;
|
|
18990
19322
|
try {
|
|
18991
19323
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -19074,17 +19406,20 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
19074
19406
|
const role = workitRole(descriptor, event.agentType);
|
|
19075
19407
|
const who = `${descriptor.label} subagent ${event.agentId} (${event.agentType})`;
|
|
19076
19408
|
const isolated = descriptor.subagents.worktreeIsolation === "native" && (role === "implementer" || CLAUDE_WORKTREE.test(input.cwd));
|
|
19409
|
+
const guide = role === "implementer" ? branchGuidance(input.cwd) : null;
|
|
19077
19410
|
if (role === "implementer" && isolated)
|
|
19078
|
-
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT}
|
|
19411
|
+
return `Workit observed ${who} working in its own git worktree: it may edit and commit there, within its brief's scope. ${ISOLATED_GIT} Plain git is expected here, and the branch name is still policy-checked: a raw \`git switch -c\`, \`git checkout -b\`, \`git branch\` or \`git worktree add -b\` onto a protected or non-compliant name is denied, so use a policy-compliant branch.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19079
19412
|
if (isolated)
|
|
19080
19413
|
return `${ISOLATED_GIT}
|
|
19081
19414
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
19082
19415
|
if (role === "implementer")
|
|
19083
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (
|
|
19416
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree (the lead makes one with \`workit fanout worktree create <slice>\`), never in the lead's checkout, within its brief's scope. Before the first commit, switch to a policy-compliant branch with \`workit git branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
19084
19417
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
19085
19418
|
if (role !== "judge" || !event.agentId)
|
|
19086
19419
|
return readOnly;
|
|
19087
19420
|
const own = subagentSession(session, event.agentId);
|
|
19421
|
+
if (session)
|
|
19422
|
+
recordIssuedSession(input.cwd || process.cwd(), session, own);
|
|
19088
19423
|
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.`;
|
|
19089
19424
|
};
|
|
19090
19425
|
function handleHook(input, deps) {
|
|
@@ -19118,7 +19453,7 @@ function handleHook(input, deps) {
|
|
|
19118
19453
|
if (skillRead)
|
|
19119
19454
|
recordSkillLoad(input, skillRead, "read");
|
|
19120
19455
|
const canDeny = usable2(descriptor.shellPolicy.deny);
|
|
19121
|
-
const policy = canDeny ? shellPolicy(input.cwd, event.command) : NONE5;
|
|
19456
|
+
const policy = canDeny ? shellPolicy(input.cwd, event.command, event.dialect) : NONE5;
|
|
19122
19457
|
if (policy.kind !== "none")
|
|
19123
19458
|
return policy;
|
|
19124
19459
|
const postTool = usable2(descriptor.events["shell.post"].support);
|
|
@@ -19716,12 +20051,12 @@ var codexAdapter = {
|
|
|
19716
20051
|
};
|
|
19717
20052
|
},
|
|
19718
20053
|
render: render2,
|
|
19719
|
-
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
|
|
20054
|
+
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>`
|
|
19720
20055
|
};
|
|
19721
20056
|
|
|
19722
20057
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
19723
|
-
import
|
|
19724
|
-
import
|
|
20058
|
+
import fs17 from "node:fs";
|
|
20059
|
+
import path23 from "node:path";
|
|
19725
20060
|
var undocumented = { support: "undocumented", native: null };
|
|
19726
20061
|
var none = { support: "none", native: null };
|
|
19727
20062
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -19918,17 +20253,17 @@ var protocolEvent2 = (input) => {
|
|
|
19918
20253
|
};
|
|
19919
20254
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
19920
20255
|
var lastAssistantText = (file) => {
|
|
19921
|
-
if (!file || !
|
|
20256
|
+
if (!file || !path23.isAbsolute(file))
|
|
19922
20257
|
return null;
|
|
19923
20258
|
try {
|
|
19924
|
-
const size =
|
|
20259
|
+
const size = fs17.statSync(file).size;
|
|
19925
20260
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
19926
20261
|
const buffer = Buffer.alloc(size - start);
|
|
19927
|
-
const fd =
|
|
20262
|
+
const fd = fs17.openSync(file, "r");
|
|
19928
20263
|
try {
|
|
19929
|
-
|
|
20264
|
+
fs17.readSync(fd, buffer, 0, buffer.length, start);
|
|
19930
20265
|
} finally {
|
|
19931
|
-
|
|
20266
|
+
fs17.closeSync(fd);
|
|
19932
20267
|
}
|
|
19933
20268
|
const lines = buffer.toString("utf8").split(`
|
|
19934
20269
|
`);
|
|
@@ -19955,7 +20290,7 @@ var lastAssistantText = (file) => {
|
|
|
19955
20290
|
return null;
|
|
19956
20291
|
}
|
|
19957
20292
|
};
|
|
19958
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
20293
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path23.sep) ? root : `${root}${path23.sep}`);
|
|
19959
20294
|
var hookCwd = (input) => {
|
|
19960
20295
|
const cwd = existingDirectory(input.cwd);
|
|
19961
20296
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|