@brainervirus/workit-codex 8.24.0 → 8.25.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/agents/workit-implementer.toml +5 -4
- package/agents/workit-verifier.toml +2 -2
- package/dist/launch-mcp.js +1650 -1493
- package/dist/{workit-hook-q465naq9.js → workit-hook-7a6v1719.js} +1734 -1463
- package/dist/workit-hook.js +1 -1
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +1 -1
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-review/SKILL.md +2 -0
- package/skills/workit-ship/SKILL.md +1 -1
|
@@ -125,7 +125,7 @@ Autonomy contract:
|
|
|
125
125
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
126
126
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
127
127
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
128
|
-
Other checkout: \`workit --cwd <dir>
|
|
128
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
129
129
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
130
130
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
131
131
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -137,7 +137,7 @@ authorizes an effect. Judge work once with
|
|
|
137
137
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
138
138
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
139
139
|
competing targets before a mutation, and reconcile an uncertain
|
|
140
|
-
|
|
140
|
+
effect before a retry.
|
|
141
141
|
|
|
142
142
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
143
143
|
|
|
@@ -7337,6 +7337,26 @@ function resolveRef(cwd, ref) {
|
|
|
7337
7337
|
return null;
|
|
7338
7338
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
7339
7339
|
}
|
|
7340
|
+
function originHeadRef(cwd) {
|
|
7341
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
7342
|
+
}
|
|
7343
|
+
function ownChangeDrift(cwd, range) {
|
|
7344
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
7345
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
7346
|
+
if (!before || !after)
|
|
7347
|
+
return null;
|
|
7348
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
7349
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
7350
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
7351
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
7352
|
+
}
|
|
7353
|
+
function sameOwnChange(cwd, range) {
|
|
7354
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
7355
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
7356
|
+
if (!fromFork || !toFork)
|
|
7357
|
+
return false;
|
|
7358
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
7359
|
+
}
|
|
7340
7360
|
var LANE_DIFF = [
|
|
7341
7361
|
"-c",
|
|
7342
7362
|
"core.quotePath=false",
|
|
@@ -12157,62 +12177,14 @@ import path15 from "node:path";
|
|
|
12157
12177
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12158
12178
|
import { createHash as createHash10, randomBytes as randomBytes3 } from "node:crypto";
|
|
12159
12179
|
import fs11 from "node:fs";
|
|
12180
|
+
import path14 from "node:path";
|
|
12181
|
+
|
|
12182
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
12183
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
12160
12184
|
import path13 from "node:path";
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
12164
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
12165
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
12166
|
-
var PR_ROW_TYPES = new Set([
|
|
12167
|
-
"pr.created",
|
|
12168
|
-
"pr.status",
|
|
12169
|
-
"pr.merged",
|
|
12170
|
-
"pr.linked"
|
|
12171
|
-
]);
|
|
12172
|
-
var err = (code, error, unblock) => ({
|
|
12173
|
-
ok: false,
|
|
12174
|
-
code,
|
|
12175
|
-
error,
|
|
12176
|
-
...unblock ? { unblock } : {}
|
|
12177
|
-
});
|
|
12178
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
12179
|
-
const run = spawnSync8("git", args, {
|
|
12180
|
-
cwd,
|
|
12181
|
-
encoding: "utf8",
|
|
12182
|
-
timeout: timeoutMs,
|
|
12183
|
-
killSignal: "SIGKILL",
|
|
12184
|
-
windowsHide: true,
|
|
12185
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
12186
|
-
env: {
|
|
12187
|
-
...process.env,
|
|
12188
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
12189
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
12190
|
-
LC_ALL: "C",
|
|
12191
|
-
LANGUAGE: "C"
|
|
12192
|
-
}
|
|
12193
|
-
});
|
|
12194
|
-
return {
|
|
12195
|
-
ok: run.status === 0,
|
|
12196
|
-
stdout: run.stdout ?? "",
|
|
12197
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
12198
|
-
};
|
|
12199
|
-
};
|
|
12200
|
-
var git3 = (cwd, args) => {
|
|
12201
|
-
const run = gitRun2(cwd, args);
|
|
12202
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
12203
|
-
return value ? value : null;
|
|
12204
|
-
};
|
|
12185
|
+
|
|
12186
|
+
// packages/workit-core/src/git/default-base.ts
|
|
12205
12187
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
12206
|
-
function resolveCommit(cwd, ref) {
|
|
12207
|
-
if (!safeRef(ref))
|
|
12208
|
-
return null;
|
|
12209
|
-
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
12210
|
-
}
|
|
12211
|
-
function branchHead(cwd, branch) {
|
|
12212
|
-
if (!safeRef(branch))
|
|
12213
|
-
return null;
|
|
12214
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
12215
|
-
}
|
|
12216
12188
|
var trackTrunk = (cwd) => {
|
|
12217
12189
|
try {
|
|
12218
12190
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -12228,11 +12200,11 @@ function defaultBase(cwd) {
|
|
|
12228
12200
|
const trunk = trackTrunk(cwd);
|
|
12229
12201
|
if (trunk) {
|
|
12230
12202
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
12231
|
-
if (
|
|
12203
|
+
if (resolveRef(cwd, candidate))
|
|
12232
12204
|
return candidate;
|
|
12233
12205
|
}
|
|
12234
|
-
const originHead =
|
|
12235
|
-
if (originHead &&
|
|
12206
|
+
const originHead = originHeadRef(cwd);
|
|
12207
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
12236
12208
|
return originHead;
|
|
12237
12209
|
for (const candidate of [
|
|
12238
12210
|
"origin/main",
|
|
@@ -12242,1475 +12214,1636 @@ function defaultBase(cwd) {
|
|
|
12242
12214
|
"origin/develop",
|
|
12243
12215
|
"develop"
|
|
12244
12216
|
])
|
|
12245
|
-
if (
|
|
12217
|
+
if (resolveRef(cwd, candidate))
|
|
12246
12218
|
return candidate;
|
|
12247
12219
|
return null;
|
|
12248
12220
|
}
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
"diff",
|
|
12254
|
-
"-U3",
|
|
12255
|
-
"--diff-algorithm=myers",
|
|
12256
|
-
"--indent-heuristic",
|
|
12257
|
-
"--no-color",
|
|
12258
|
-
"--no-ext-diff",
|
|
12259
|
-
"--no-textconv",
|
|
12260
|
-
"--no-renames",
|
|
12261
|
-
"--full-index",
|
|
12262
|
-
"--binary",
|
|
12263
|
-
"--src-prefix=a/",
|
|
12264
|
-
"--dst-prefix=b/",
|
|
12265
|
-
`${base}...${head}`,
|
|
12266
|
-
"--"
|
|
12267
|
-
], GIT_TIMEOUTS.worktree);
|
|
12268
|
-
if (!diff.ok || !diff.stdout)
|
|
12221
|
+
|
|
12222
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
12223
|
+
function patternRegex(dir, pattern) {
|
|
12224
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
12269
12225
|
return null;
|
|
12270
|
-
const
|
|
12271
|
-
|
|
12272
|
-
|
|
12273
|
-
|
|
12274
|
-
|
|
12275
|
-
|
|
12276
|
-
|
|
12277
|
-
|
|
12278
|
-
|
|
12279
|
-
|
|
12280
|
-
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
}
|
|
12291
|
-
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
4266872130,
|
|
12296
|
-
4283649346,
|
|
12297
|
-
1702057286,
|
|
12298
|
-
1397113167,
|
|
12299
|
-
12805120,
|
|
12300
|
-
16914839,
|
|
12301
|
-
1196443219,
|
|
12302
|
-
198183888
|
|
12303
|
-
]);
|
|
12304
|
-
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
12305
|
-
var onNetworkFs = (dir) => {
|
|
12306
|
-
if (process.platform !== "linux")
|
|
12307
|
-
return false;
|
|
12308
|
-
try {
|
|
12309
|
-
return isNetworkFsType(fs11.statfsSync(dir).type);
|
|
12310
|
-
} catch {
|
|
12311
|
-
return false;
|
|
12226
|
+
const anchored = pattern.includes("/");
|
|
12227
|
+
const body = pattern.replace(/^\//u, "");
|
|
12228
|
+
let source = "";
|
|
12229
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
12230
|
+
const char = body[index];
|
|
12231
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
12232
|
+
const slash = body[index + 2] === "/";
|
|
12233
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
12234
|
+
index += slash ? 2 : 1;
|
|
12235
|
+
} else if (char === "*")
|
|
12236
|
+
source += "[^/]*";
|
|
12237
|
+
else if (char === "?")
|
|
12238
|
+
source += "[^/]";
|
|
12239
|
+
else if (char === "[") {
|
|
12240
|
+
const end = body.indexOf("]", index + 2);
|
|
12241
|
+
if (end < 0)
|
|
12242
|
+
return null;
|
|
12243
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
12244
|
+
source += `[${set}]`;
|
|
12245
|
+
index = end;
|
|
12246
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
12247
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
12248
|
+
index += 1;
|
|
12249
|
+
} else
|
|
12250
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
12312
12251
|
}
|
|
12313
|
-
};
|
|
12314
|
-
var LOCK_WAIT_MS = 5000;
|
|
12315
|
-
var LOCK_STALE_MS = 30000;
|
|
12316
|
-
var LOCK_OWNER = "owner";
|
|
12317
|
-
var lockToken = (lock) => {
|
|
12252
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
12318
12253
|
try {
|
|
12319
|
-
return
|
|
12254
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
12320
12255
|
} catch {
|
|
12321
12256
|
return null;
|
|
12322
12257
|
}
|
|
12323
|
-
}
|
|
12324
|
-
|
|
12325
|
-
|
|
12326
|
-
|
|
12327
|
-
|
|
12328
|
-
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
return null;
|
|
12339
|
-
throw error;
|
|
12340
|
-
}
|
|
12341
|
-
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
12342
|
-
fs11.writeFileSync(path13.join(lock, LOCK_OWNER), token);
|
|
12343
|
-
return token;
|
|
12344
|
-
},
|
|
12345
|
-
release(lock, token) {
|
|
12346
|
-
if (lockToken(lock) !== token)
|
|
12347
|
-
return false;
|
|
12348
|
-
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
12349
|
-
try {
|
|
12350
|
-
fs11.renameSync(lock, tomb);
|
|
12351
|
-
} catch {
|
|
12352
|
-
return false;
|
|
12353
|
-
}
|
|
12354
|
-
if (lockToken(tomb) !== token) {
|
|
12355
|
-
try {
|
|
12356
|
-
fs11.renameSync(tomb, lock);
|
|
12357
|
-
} catch {}
|
|
12358
|
-
return false;
|
|
12359
|
-
}
|
|
12360
|
-
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
12361
|
-
return true;
|
|
12362
|
-
},
|
|
12363
|
-
token: lockToken,
|
|
12364
|
-
reap(lock, observed) {
|
|
12365
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
12366
|
-
try {
|
|
12367
|
-
fs11.renameSync(lock, tomb);
|
|
12368
|
-
} catch {
|
|
12369
|
-
return false;
|
|
12370
|
-
}
|
|
12371
|
-
if (lockToken(tomb) === observed) {
|
|
12372
|
-
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
12373
|
-
return true;
|
|
12374
|
-
}
|
|
12375
|
-
try {
|
|
12376
|
-
fs11.renameSync(tomb, lock);
|
|
12377
|
-
} catch {
|
|
12378
|
-
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
12258
|
+
}
|
|
12259
|
+
function rulesOf(dir, text) {
|
|
12260
|
+
const out = [];
|
|
12261
|
+
for (const raw of text.split(`
|
|
12262
|
+
`)) {
|
|
12263
|
+
const line = raw.trim();
|
|
12264
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
12265
|
+
continue;
|
|
12266
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
12267
|
+
let generated = null;
|
|
12268
|
+
for (const attribute of attributes) {
|
|
12269
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
12270
|
+
generated = true;
|
|
12271
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
12272
|
+
generated = false;
|
|
12379
12273
|
}
|
|
12380
|
-
|
|
12381
|
-
}
|
|
12382
|
-
};
|
|
12383
|
-
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
12384
|
-
const lock = `${file}.lock`;
|
|
12385
|
-
const deadline = Date.now() + waitMs;
|
|
12386
|
-
let token = null;
|
|
12387
|
-
for (;; ) {
|
|
12388
|
-
token = ledgerLock.acquire(lock);
|
|
12389
|
-
if (token)
|
|
12390
|
-
break;
|
|
12391
|
-
const observed = lockToken(lock);
|
|
12392
|
-
const age = lockAgeMs(lock);
|
|
12393
|
-
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
12274
|
+
if (generated === null)
|
|
12394
12275
|
continue;
|
|
12395
|
-
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
}
|
|
12399
|
-
try {
|
|
12400
|
-
return fn();
|
|
12401
|
-
} finally {
|
|
12402
|
-
ledgerLock.release(lock, token);
|
|
12276
|
+
const regex = patternRegex(dir, pattern);
|
|
12277
|
+
if (regex)
|
|
12278
|
+
out.push({ regex, generated });
|
|
12403
12279
|
}
|
|
12280
|
+
return out;
|
|
12404
12281
|
}
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
|
|
12414
|
-
|
|
12415
|
-
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
}
|
|
12419
|
-
let stop = end;
|
|
12420
|
-
let text = "";
|
|
12421
|
-
while (stop >= 0) {
|
|
12422
|
-
const start = buffer.lastIndexOf(10, stop - 1);
|
|
12423
|
-
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
12424
|
-
if (text) {
|
|
12425
|
-
if (start >= 0 || span === size)
|
|
12426
|
-
return text;
|
|
12427
|
-
break;
|
|
12428
|
-
}
|
|
12429
|
-
if (start < 0) {
|
|
12430
|
-
if (span === size)
|
|
12431
|
-
return null;
|
|
12432
|
-
break;
|
|
12433
|
-
}
|
|
12434
|
-
stop = start;
|
|
12435
|
-
}
|
|
12436
|
-
if (span === size)
|
|
12437
|
-
return text || null;
|
|
12438
|
-
}
|
|
12439
|
-
return null;
|
|
12440
|
-
};
|
|
12441
|
-
var tighten = (fd) => {
|
|
12442
|
-
try {
|
|
12443
|
-
if ((fs11.fstatSync(fd).mode & 63) !== 0)
|
|
12444
|
-
fs11.fchmodSync(fd, 384);
|
|
12445
|
-
} catch {}
|
|
12446
|
-
};
|
|
12447
|
-
function appendLine(file, build, options) {
|
|
12448
|
-
const write = () => {
|
|
12449
|
-
const fd = fs11.openSync(file, "a+", 384);
|
|
12450
|
-
try {
|
|
12451
|
-
if (process.platform !== "win32")
|
|
12452
|
-
tighten(fd);
|
|
12453
|
-
const size = fs11.fstatSync(fd).size;
|
|
12454
|
-
let fence = "";
|
|
12455
|
-
if (size > 0) {
|
|
12456
|
-
const last = Buffer.alloc(1);
|
|
12457
|
-
fs11.readSync(fd, last, 0, 1, size - 1);
|
|
12458
|
-
if (last[0] !== 10)
|
|
12459
|
-
fence = `
|
|
12460
|
-
`;
|
|
12461
|
-
}
|
|
12462
|
-
const line = build(lastLine(fd, size));
|
|
12463
|
-
if (!line.ok)
|
|
12464
|
-
return line;
|
|
12465
|
-
const bytes = Buffer.from(fence + line.value);
|
|
12466
|
-
const written = fs11.writeSync(fd, bytes);
|
|
12467
|
-
if (written !== bytes.length)
|
|
12468
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
12469
|
-
return { ok: true, value: undefined };
|
|
12470
|
-
} finally {
|
|
12471
|
-
fs11.closeSync(fd);
|
|
12472
|
-
}
|
|
12473
|
-
};
|
|
12474
|
-
try {
|
|
12475
|
-
fs11.mkdirSync(path13.dirname(file), { recursive: true });
|
|
12476
|
-
if (!options.forceLock && !onNetworkFs(path13.dirname(file)))
|
|
12477
|
-
return write();
|
|
12478
|
-
const locked = withLedgerLock(file, write);
|
|
12479
|
-
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
12480
|
-
} catch (error) {
|
|
12481
|
-
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
12282
|
+
function generatedByAttributes(files, paths) {
|
|
12283
|
+
const rules = [...files].map(([file, text]) => {
|
|
12284
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
12285
|
+
return {
|
|
12286
|
+
depth: dir ? dir.split("/").length : 0,
|
|
12287
|
+
rules: rulesOf(dir, text)
|
|
12288
|
+
};
|
|
12289
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
12290
|
+
const out = new Set;
|
|
12291
|
+
for (const file of paths) {
|
|
12292
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
12293
|
+
if (last?.generated)
|
|
12294
|
+
out.add(file);
|
|
12482
12295
|
}
|
|
12296
|
+
return out;
|
|
12483
12297
|
}
|
|
12484
|
-
|
|
12485
|
-
|
|
12486
|
-
|
|
12487
|
-
|
|
12488
|
-
|
|
12489
|
-
|
|
12490
|
-
|
|
12298
|
+
|
|
12299
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
12300
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
12301
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
12302
|
+
var SYNTAXES = Object.fromEntries([
|
|
12303
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
12304
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
12305
|
+
ext,
|
|
12306
|
+
{ ...C_LIKE, triple: true }
|
|
12307
|
+
]),
|
|
12308
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
12309
|
+
...["css", "scss", "less"].map((ext) => [
|
|
12310
|
+
ext,
|
|
12311
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
12312
|
+
]),
|
|
12313
|
+
...["py", "pyi"].map((ext) => [
|
|
12314
|
+
ext,
|
|
12315
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
12316
|
+
]),
|
|
12317
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
12318
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
12319
|
+
ext,
|
|
12320
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
12321
|
+
])
|
|
12322
|
+
]);
|
|
12323
|
+
var DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
12324
|
+
var extensionOf = (file) => {
|
|
12325
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
12326
|
+
const dot = name.lastIndexOf(".");
|
|
12327
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
12491
12328
|
};
|
|
12492
|
-
|
|
12493
|
-
|
|
12494
|
-
|
|
12495
|
-
|
|
12496
|
-
|
|
12497
|
-
|
|
12498
|
-
|
|
12499
|
-
|
|
12500
|
-
|
|
12501
|
-
|
|
12502
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
12505
|
-
|
|
12506
|
-
|
|
12507
|
-
|
|
12508
|
-
|
|
12509
|
-
|
|
12510
|
-
|
|
12511
|
-
|
|
12512
|
-
|
|
12513
|
-
|
|
12514
|
-
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12329
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
12330
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
12331
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
12332
|
+
var WORD = /[\w$]/u;
|
|
12333
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
12334
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
12335
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
12336
|
+
var REGEX_KEYWORDS = new Set([
|
|
12337
|
+
"return",
|
|
12338
|
+
"typeof",
|
|
12339
|
+
"instanceof",
|
|
12340
|
+
"case",
|
|
12341
|
+
"do",
|
|
12342
|
+
"else",
|
|
12343
|
+
"in",
|
|
12344
|
+
"of",
|
|
12345
|
+
"new",
|
|
12346
|
+
"delete",
|
|
12347
|
+
"void",
|
|
12348
|
+
"throw",
|
|
12349
|
+
"yield",
|
|
12350
|
+
"await"
|
|
12351
|
+
]);
|
|
12352
|
+
function substanceOf(file, text) {
|
|
12353
|
+
const syntax = syntaxOf(file);
|
|
12354
|
+
if (!syntax)
|
|
12355
|
+
return plainSubstance(text);
|
|
12356
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
12357
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
12358
|
+
return plainSubstance(text);
|
|
12359
|
+
return lex(syntax, text);
|
|
12518
12360
|
}
|
|
12519
|
-
|
|
12520
|
-
|
|
12521
|
-
|
|
12522
|
-
|
|
12523
|
-
return {
|
|
12524
|
-
host: str(actor.host) ?? "unknown",
|
|
12525
|
-
session: str(actor.session),
|
|
12526
|
-
agentId: str(actor.agentId)
|
|
12527
|
-
};
|
|
12528
|
-
};
|
|
12529
|
-
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
12530
|
-
if (by.type !== target.type)
|
|
12531
|
-
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
12532
|
-
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
12533
|
-
return "only the session that wrote a row can supersede it";
|
|
12534
|
-
if (!onBranch(target))
|
|
12535
|
-
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
12536
|
-
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
12537
|
-
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
12538
|
-
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
12539
|
-
return "a self verdict cannot replace an independent verdict";
|
|
12540
|
-
return null;
|
|
12361
|
+
function plainSubstance(text) {
|
|
12362
|
+
return text.split(`
|
|
12363
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
12364
|
+
`);
|
|
12541
12365
|
}
|
|
12542
|
-
function
|
|
12543
|
-
const
|
|
12544
|
-
|
|
12545
|
-
|
|
12546
|
-
let
|
|
12547
|
-
let
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
|
|
12556
|
-
path: file.value,
|
|
12557
|
-
rows: [],
|
|
12558
|
-
skipped: 0,
|
|
12559
|
-
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
12560
|
-
}
|
|
12561
|
-
};
|
|
12562
|
-
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
12366
|
+
function lex(syntax, text) {
|
|
12367
|
+
const collapse = !syntax.indented;
|
|
12368
|
+
let out = "";
|
|
12369
|
+
let lineStart = 0;
|
|
12370
|
+
let kept = 0;
|
|
12371
|
+
let pendingSpace = false;
|
|
12372
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
12373
|
+
let index = 0;
|
|
12374
|
+
if (text.startsWith("#!")) {
|
|
12375
|
+
const end = text.indexOf(`
|
|
12376
|
+
`);
|
|
12377
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
12378
|
+
kept = out.length;
|
|
12379
|
+
index = end < 0 ? text.length : end;
|
|
12563
12380
|
}
|
|
12564
|
-
const
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
const ids = new Set;
|
|
12570
|
-
for (const raw of text.split(`
|
|
12571
|
-
`)) {
|
|
12572
|
-
const trimmed = raw.trim();
|
|
12573
|
-
if (!trimmed)
|
|
12574
|
-
continue;
|
|
12575
|
-
const linked = (prevHash) => earlier.has(prevHash);
|
|
12576
|
-
if (options.integrity)
|
|
12577
|
-
earlier.add(sha2562(trimmed));
|
|
12578
|
-
let parsed;
|
|
12579
|
-
try {
|
|
12580
|
-
parsed = JSON.parse(trimmed);
|
|
12581
|
-
} catch {
|
|
12582
|
-
skipped += 1;
|
|
12583
|
-
continue;
|
|
12584
|
-
}
|
|
12585
|
-
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
12586
|
-
skipped += 1;
|
|
12587
|
-
continue;
|
|
12381
|
+
const emitCode = (chunk) => {
|
|
12382
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
12383
|
+
const before = out[out.length - 1];
|
|
12384
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
12385
|
+
out += " ";
|
|
12588
12386
|
}
|
|
12589
|
-
|
|
12590
|
-
|
|
12591
|
-
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
12592
|
-
chainStarted = true;
|
|
12593
|
-
const { rowHash, ...body } = parsed;
|
|
12594
|
-
const prevHash = str(parsed.prevHash);
|
|
12595
|
-
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
12596
|
-
reason = "hash_mismatch";
|
|
12597
|
-
else if (!linked(prevHash))
|
|
12598
|
-
reason = "broken_link";
|
|
12599
|
-
} else if (chainStarted)
|
|
12600
|
-
reason = "unsigned";
|
|
12601
|
-
else
|
|
12602
|
-
rowIntegrity = "legacy";
|
|
12603
|
-
if (options.integrity && !reason && ids.has(parsed.id))
|
|
12604
|
-
reason = "duplicate_id";
|
|
12605
|
-
if (options.integrity)
|
|
12606
|
-
ids.add(parsed.id);
|
|
12607
|
-
if (reason)
|
|
12608
|
-
rowIntegrity = "unverified";
|
|
12609
|
-
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
12610
|
-
const row = {
|
|
12611
|
-
...parsed,
|
|
12612
|
-
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
12613
|
-
id: parsed.id,
|
|
12614
|
-
at: parsed.at,
|
|
12615
|
-
type: parsed.type,
|
|
12616
|
-
actor: normalizeActor(parsed.actor),
|
|
12617
|
-
branch: str(parsed.branch),
|
|
12618
|
-
head: str(parsed.head),
|
|
12619
|
-
base: str(parsed.base),
|
|
12620
|
-
baseSha: str(parsed.baseSha),
|
|
12621
|
-
patchId: str(parsed.patchId),
|
|
12622
|
-
diffHash: str(parsed.diffHash),
|
|
12623
|
-
tree: str(parsed.tree),
|
|
12624
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
12625
|
-
...pr === undefined ? {} : { pr },
|
|
12626
|
-
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
12627
|
-
seq: rows.length + 1,
|
|
12628
|
-
superseded: false,
|
|
12629
|
-
supersedeIgnored: false,
|
|
12630
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
12631
|
-
...reason ? { integrityReason: reason } : {}
|
|
12632
|
-
};
|
|
12633
|
-
rows.push(row);
|
|
12634
|
-
if (!options.integrity)
|
|
12635
|
-
continue;
|
|
12636
|
-
if (rowIntegrity === "verified")
|
|
12637
|
-
integrity.chained += 1;
|
|
12638
|
-
else if (rowIntegrity === "legacy")
|
|
12639
|
-
integrity.legacy += 1;
|
|
12640
|
-
else if (reason)
|
|
12641
|
-
integrity.unverified.push({
|
|
12642
|
-
seq: row.seq,
|
|
12643
|
-
id: row.id,
|
|
12644
|
-
type: row.type,
|
|
12645
|
-
branch: row.branch,
|
|
12646
|
-
reason
|
|
12647
|
-
});
|
|
12648
|
-
}
|
|
12649
|
-
const scopes = new Map;
|
|
12650
|
-
const scopeOf = (branch) => {
|
|
12651
|
-
let scope = scopes.get(branch);
|
|
12652
|
-
if (!scope)
|
|
12653
|
-
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
12654
|
-
return scope;
|
|
12387
|
+
pendingSpace = false;
|
|
12388
|
+
out += chunk;
|
|
12655
12389
|
};
|
|
12656
|
-
const
|
|
12657
|
-
|
|
12658
|
-
|
|
12659
|
-
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
12660
|
-
target.superseded = true;
|
|
12661
|
-
else if (row.supersedes)
|
|
12662
|
-
row.supersedeIgnored = true;
|
|
12663
|
-
if (!byId.has(row.id))
|
|
12664
|
-
byId.set(row.id, row);
|
|
12665
|
-
}
|
|
12666
|
-
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
12667
|
-
tamperCache.set(file.value, {
|
|
12668
|
-
size: stat.size,
|
|
12669
|
-
stamp: fileStamp(stat),
|
|
12670
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
12671
|
-
});
|
|
12672
|
-
return {
|
|
12673
|
-
ok: true,
|
|
12674
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
12390
|
+
const emitVerbatim = (chunk) => {
|
|
12391
|
+
emitCode(chunk);
|
|
12392
|
+
kept = out.length;
|
|
12675
12393
|
};
|
|
12676
|
-
|
|
12677
|
-
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12685
|
-
|
|
12686
|
-
|
|
12687
|
-
|
|
12688
|
-
return cached.breaks;
|
|
12689
|
-
} catch {
|
|
12690
|
-
return [];
|
|
12691
|
-
}
|
|
12692
|
-
const read = readLedger(cwd, { integrity: true });
|
|
12693
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
12694
|
-
}
|
|
12695
|
-
function tamperedUnblock(cwd) {
|
|
12696
|
-
const file = ledgerPath(cwd);
|
|
12697
|
-
const where = file.ok ? file.value : "the ledger file";
|
|
12698
|
-
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
12699
|
-
}
|
|
12700
|
-
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
12701
|
-
function verdictBasis(verdict, current) {
|
|
12702
|
-
if (verdict.head && verdict.head === current.head)
|
|
12703
|
-
return "fresh";
|
|
12704
|
-
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
12705
|
-
return "carried";
|
|
12706
|
-
return "stale";
|
|
12707
|
-
}
|
|
12708
|
-
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
12709
|
-
function trailerSessions(cwd, base, head) {
|
|
12710
|
-
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
12711
|
-
return [];
|
|
12712
|
-
const out = git3(cwd, [
|
|
12713
|
-
"log",
|
|
12714
|
-
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
12715
|
-
`${base}..${head}`
|
|
12716
|
-
]);
|
|
12717
|
-
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
12718
|
-
}
|
|
12719
|
-
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
12720
|
-
function branchScope(cwd, branch) {
|
|
12721
|
-
const renamedAt = new Map;
|
|
12722
|
-
let since = Number.NEGATIVE_INFINITY;
|
|
12723
|
-
if (safeRef(branch)) {
|
|
12724
|
-
const log = git3(cwd, [
|
|
12725
|
-
"log",
|
|
12726
|
-
"-g",
|
|
12727
|
-
"--date=unix",
|
|
12728
|
-
"--format=%gd%x09%gs",
|
|
12729
|
-
`refs/heads/${branch}`,
|
|
12730
|
-
"--"
|
|
12731
|
-
]);
|
|
12732
|
-
for (const line of (log ?? "").split(`
|
|
12733
|
-
`)) {
|
|
12734
|
-
const [selector = "", subject = ""] = line.split("\t");
|
|
12735
|
-
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
12736
|
-
if (!Number.isFinite(seconds))
|
|
12737
|
-
continue;
|
|
12738
|
-
since = seconds * 1000;
|
|
12739
|
-
const match = RENAMED.exec(subject);
|
|
12740
|
-
if (!match || match[1] === branch)
|
|
12741
|
-
continue;
|
|
12742
|
-
const until = (seconds + 1) * 1000;
|
|
12743
|
-
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
12394
|
+
const newline = () => {
|
|
12395
|
+
if (!collapse) {
|
|
12396
|
+
let end = out.length;
|
|
12397
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
12398
|
+
end -= 1;
|
|
12399
|
+
out = out.slice(0, end);
|
|
12400
|
+
}
|
|
12401
|
+
pendingSpace = false;
|
|
12402
|
+
if (out.length > lineStart) {
|
|
12403
|
+
out += `
|
|
12404
|
+
`;
|
|
12405
|
+
lineStart = out.length;
|
|
12744
12406
|
}
|
|
12745
|
-
}
|
|
12746
|
-
return (row) => {
|
|
12747
|
-
if (row.branch === branch)
|
|
12748
|
-
return true;
|
|
12749
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
12750
|
-
if (until === undefined)
|
|
12751
|
-
return false;
|
|
12752
|
-
const at = Date.parse(row.at);
|
|
12753
|
-
return at >= since && at <= until;
|
|
12754
12407
|
};
|
|
12755
|
-
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
if (own === theirs)
|
|
12408
|
+
const regexAllowed = () => {
|
|
12409
|
+
let end = out.length;
|
|
12410
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
12411
|
+
end -= 1;
|
|
12412
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
12761
12413
|
return true;
|
|
12762
|
-
|
|
12763
|
-
|
|
12764
|
-
|
|
12414
|
+
let start = end;
|
|
12415
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
12416
|
+
start -= 1;
|
|
12417
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
12765
12418
|
};
|
|
12766
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
12769
|
-
|
|
12770
|
-
|
|
12771
|
-
|
|
12772
|
-
|
|
12773
|
-
|
|
12774
|
-
|
|
12775
|
-
|
|
12776
|
-
|
|
12777
|
-
|
|
12778
|
-
|
|
12779
|
-
|
|
12780
|
-
|
|
12781
|
-
|
|
12782
|
-
|
|
12783
|
-
|
|
12784
|
-
|
|
12785
|
-
|
|
12786
|
-
|
|
12787
|
-
return false;
|
|
12788
|
-
};
|
|
12789
|
-
var TRUST_REASONS = new Set([
|
|
12790
|
-
"unsigned",
|
|
12791
|
-
"hash_mismatch",
|
|
12792
|
-
"broken_link",
|
|
12793
|
-
"duplicate_id",
|
|
12794
|
-
"unissued_session",
|
|
12795
|
-
"unbound_session"
|
|
12796
|
-
]);
|
|
12797
|
-
var SHELL_VOUCHED_HOSTS = new Set([
|
|
12798
|
-
"cli",
|
|
12799
|
-
"opencode",
|
|
12800
|
-
"pi",
|
|
12801
|
-
"codex_cli",
|
|
12802
|
-
"codex_desktop"
|
|
12803
|
-
]);
|
|
12804
|
-
function sessionTrust(cwd, row) {
|
|
12805
|
-
const session = row.actor.session;
|
|
12806
|
-
if (!session || row.self === true)
|
|
12807
|
-
return null;
|
|
12808
|
-
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
12809
|
-
const host = row.actor.host;
|
|
12810
|
-
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
12811
|
-
if (!issuedOnly)
|
|
12812
|
-
return null;
|
|
12813
|
-
const binding = issuedBinding(cwd, session);
|
|
12814
|
-
if (binding === null)
|
|
12815
|
-
return "unissued_session";
|
|
12816
|
-
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
12817
|
-
}
|
|
12818
|
-
function trustReasons(cwd, row) {
|
|
12819
|
-
if (row.integrity === "unverified")
|
|
12820
|
-
return [row.integrityReason ?? "unsigned"];
|
|
12821
|
-
if (row.integrity !== "verified")
|
|
12822
|
-
return ["unsigned"];
|
|
12823
|
-
const session = sessionTrust(cwd, row);
|
|
12824
|
-
return session ? [session] : [];
|
|
12825
|
-
}
|
|
12826
|
-
function withIntegrity(cwd, rows) {
|
|
12827
|
-
if (rows.every((row) => row.integrity !== undefined))
|
|
12828
|
-
return rows;
|
|
12829
|
-
const read = readLedger(cwd, { integrity: true });
|
|
12830
|
-
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
12831
|
-
return rows.map((row) => {
|
|
12832
|
-
if (row.integrity !== undefined)
|
|
12833
|
-
return row;
|
|
12834
|
-
const fresh = bySeq.get(row.seq);
|
|
12835
|
-
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
12836
|
-
});
|
|
12837
|
-
}
|
|
12838
|
-
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
12839
|
-
var independenceReasons = (row, authors) => {
|
|
12840
|
-
const reasons = [];
|
|
12841
|
-
if (!row.actor.session)
|
|
12842
|
-
reasons.push("no_session");
|
|
12843
|
-
else if (authoredBy(authors, row.actor.session))
|
|
12844
|
-
reasons.push("author_session");
|
|
12845
|
-
if (row.self === true && !reasons.includes("no_session"))
|
|
12846
|
-
reasons.push("self");
|
|
12847
|
-
return reasons;
|
|
12848
|
-
};
|
|
12849
|
-
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
12850
|
-
let effective = null;
|
|
12851
|
-
for (const row of rows) {
|
|
12852
|
-
if (!effective) {
|
|
12853
|
-
effective = row;
|
|
12854
|
-
continue;
|
|
12419
|
+
const scanString = (quote, escapes, multiline) => {
|
|
12420
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
12421
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
12422
|
+
let end = index + quote.length;
|
|
12423
|
+
while (end < text.length) {
|
|
12424
|
+
const char = text[end];
|
|
12425
|
+
if (escapes && char === "\\") {
|
|
12426
|
+
end += 2;
|
|
12427
|
+
continue;
|
|
12428
|
+
}
|
|
12429
|
+
if (text.startsWith(quote, end)) {
|
|
12430
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
12431
|
+
return false;
|
|
12432
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
12433
|
+
index = end + quote.length;
|
|
12434
|
+
return true;
|
|
12435
|
+
}
|
|
12436
|
+
if (char === `
|
|
12437
|
+
` && !multiline)
|
|
12438
|
+
return false;
|
|
12439
|
+
end += 1;
|
|
12855
12440
|
}
|
|
12856
|
-
|
|
12441
|
+
return false;
|
|
12442
|
+
};
|
|
12443
|
+
while (index < text.length) {
|
|
12444
|
+
const frame = frames[frames.length - 1];
|
|
12445
|
+
const char = text[index];
|
|
12446
|
+
if (frame.kind === "template") {
|
|
12447
|
+
if (char === "\\") {
|
|
12448
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
12449
|
+
index += 2;
|
|
12450
|
+
} else if (char === "`") {
|
|
12451
|
+
emitVerbatim(char);
|
|
12452
|
+
frames.pop();
|
|
12453
|
+
index += 1;
|
|
12454
|
+
} else if (text.startsWith("${", index)) {
|
|
12455
|
+
emitVerbatim("${");
|
|
12456
|
+
frames.push({ kind: "code", braces: 0 });
|
|
12457
|
+
index += 2;
|
|
12458
|
+
} else {
|
|
12459
|
+
out += char;
|
|
12460
|
+
kept = out.length;
|
|
12461
|
+
index += 1;
|
|
12462
|
+
}
|
|
12857
12463
|
continue;
|
|
12858
|
-
|
|
12859
|
-
|
|
12860
|
-
|
|
12464
|
+
}
|
|
12465
|
+
if (char === `
|
|
12466
|
+
`) {
|
|
12467
|
+
newline();
|
|
12468
|
+
index += 1;
|
|
12861
12469
|
continue;
|
|
12862
|
-
|
|
12470
|
+
}
|
|
12471
|
+
if (SPACE.test(char)) {
|
|
12472
|
+
if (collapse)
|
|
12473
|
+
pendingSpace = true;
|
|
12474
|
+
else
|
|
12475
|
+
out += char;
|
|
12476
|
+
index += 1;
|
|
12863
12477
|
continue;
|
|
12864
|
-
effective = row;
|
|
12865
|
-
}
|
|
12866
|
-
return effective;
|
|
12867
|
-
}
|
|
12868
|
-
function restackCarries(rows, branch, from, to) {
|
|
12869
|
-
if (!from || !to)
|
|
12870
|
-
return false;
|
|
12871
|
-
const edges = new Map;
|
|
12872
|
-
for (const row of rows)
|
|
12873
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
12874
|
-
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
12875
|
-
const seen = new Set([from]);
|
|
12876
|
-
const queue = [from];
|
|
12877
|
-
while (queue.length) {
|
|
12878
|
-
const next = queue.shift();
|
|
12879
|
-
if (next === to)
|
|
12880
|
-
return true;
|
|
12881
|
-
for (const target of edges.get(next) ?? [])
|
|
12882
|
-
if (!seen.has(target)) {
|
|
12883
|
-
seen.add(target);
|
|
12884
|
-
queue.push(target);
|
|
12885
|
-
}
|
|
12886
|
-
}
|
|
12887
|
-
return false;
|
|
12888
|
-
}
|
|
12889
|
-
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
12890
|
-
const rows = withIntegrity(cwd, ledgerRows);
|
|
12891
|
-
const head = branchHead(cwd, branch);
|
|
12892
|
-
const fallbackBase = defaultBase(cwd);
|
|
12893
|
-
const scope = branchScope(cwd, branch);
|
|
12894
|
-
const keyCache = new Map;
|
|
12895
|
-
const keysAt = (base, at) => {
|
|
12896
|
-
if (!base || !at)
|
|
12897
|
-
return { patchId: null, diffHash: null };
|
|
12898
|
-
const cacheKey = `${base}\x00${at}`;
|
|
12899
|
-
let key = keyCache.get(cacheKey);
|
|
12900
|
-
if (!key) {
|
|
12901
|
-
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
12902
|
-
keyCache.set(cacheKey, key);
|
|
12903
12478
|
}
|
|
12904
|
-
|
|
12905
|
-
|
|
12906
|
-
|
|
12907
|
-
|
|
12908
|
-
|
|
12909
|
-
|
|
12910
|
-
|
|
12911
|
-
|
|
12912
|
-
|
|
12913
|
-
|
|
12914
|
-
|
|
12915
|
-
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
12916
|
-
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
12917
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
12918
|
-
};
|
|
12919
|
-
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
12920
|
-
const failureAuthorCache = new Map;
|
|
12921
|
-
const failureAuthors = (row) => {
|
|
12922
|
-
let set = failureAuthorCache.get(row.seq);
|
|
12923
|
-
if (!set) {
|
|
12924
|
-
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
12925
|
-
failureAuthorCache.set(row.seq, set);
|
|
12479
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
12480
|
+
if (lineOpener) {
|
|
12481
|
+
const end = text.indexOf(`
|
|
12482
|
+
`, index);
|
|
12483
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
12484
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
12485
|
+
emitVerbatim(comment);
|
|
12486
|
+
else
|
|
12487
|
+
pendingSpace = true;
|
|
12488
|
+
index = end < 0 ? text.length : end;
|
|
12489
|
+
continue;
|
|
12926
12490
|
}
|
|
12927
|
-
|
|
12928
|
-
|
|
12929
|
-
|
|
12930
|
-
|
|
12931
|
-
|
|
12932
|
-
|
|
12933
|
-
|
|
12934
|
-
|
|
12935
|
-
|
|
12936
|
-
|
|
12937
|
-
|
|
12938
|
-
|
|
12491
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
12492
|
+
if (block) {
|
|
12493
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
12494
|
+
if (end < 0)
|
|
12495
|
+
return null;
|
|
12496
|
+
const comment = text.slice(index, end + block[1].length);
|
|
12497
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
12498
|
+
emitVerbatim(comment);
|
|
12499
|
+
else
|
|
12500
|
+
pendingSpace = true;
|
|
12501
|
+
if (comment.includes(`
|
|
12502
|
+
`))
|
|
12503
|
+
newline();
|
|
12504
|
+
index = end + block[1].length;
|
|
12505
|
+
continue;
|
|
12939
12506
|
}
|
|
12940
|
-
|
|
12941
|
-
|
|
12942
|
-
|
|
12943
|
-
|
|
12944
|
-
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
12945
|
-
retired.add(row.supersedes);
|
|
12946
|
-
const byKind = new Map;
|
|
12947
|
-
for (const row of rows)
|
|
12948
|
-
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
12949
|
-
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
12950
|
-
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
12507
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
12508
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
12509
|
+
return null;
|
|
12510
|
+
continue;
|
|
12951
12511
|
}
|
|
12952
|
-
|
|
12953
|
-
|
|
12954
|
-
|
|
12955
|
-
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
12956
|
-
if (!row)
|
|
12512
|
+
if (syntax.quotes.includes(char)) {
|
|
12513
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
12514
|
+
return null;
|
|
12957
12515
|
continue;
|
|
12958
|
-
const basis = basisOf(row);
|
|
12959
|
-
const current = basis !== "stale";
|
|
12960
|
-
const independence = reasonsOf(row);
|
|
12961
|
-
const reasons = [
|
|
12962
|
-
...current ? [] : ["stale"],
|
|
12963
|
-
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
12964
|
-
...independence
|
|
12965
|
-
];
|
|
12966
|
-
verdicts.push({
|
|
12967
|
-
kind,
|
|
12968
|
-
basis,
|
|
12969
|
-
current,
|
|
12970
|
-
independent: independence.length === 0,
|
|
12971
|
-
accepted: reasons.length === 0,
|
|
12972
|
-
reasons,
|
|
12973
|
-
verdict: row
|
|
12974
|
-
});
|
|
12975
|
-
if (reasons.length === 0) {
|
|
12976
|
-
for (const later of list)
|
|
12977
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
12978
|
-
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
12979
12516
|
}
|
|
12980
|
-
|
|
12981
|
-
|
|
12982
|
-
|
|
12983
|
-
|
|
12984
|
-
const tampered = ledgerTampering(cwd);
|
|
12985
|
-
if (tampered.length) {
|
|
12986
|
-
warnings.push(tamperedWarning(cwd, tampered));
|
|
12987
|
-
for (const entry of verdicts) {
|
|
12988
|
-
entry.accepted = false;
|
|
12989
|
-
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
12517
|
+
if (syntax.literal?.includes(char)) {
|
|
12518
|
+
if (!scanString(char, false, false))
|
|
12519
|
+
return null;
|
|
12520
|
+
continue;
|
|
12990
12521
|
}
|
|
12991
|
-
|
|
12992
|
-
|
|
12993
|
-
|
|
12994
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
12995
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
12996
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
12997
|
-
accepted: false,
|
|
12998
|
-
verdict: failing?.verdict ?? null,
|
|
12999
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
13000
|
-
};
|
|
13001
|
-
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
13002
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
13003
|
-
return {
|
|
13004
|
-
branch,
|
|
13005
|
-
head,
|
|
13006
|
-
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
13007
|
-
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
13008
|
-
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
13009
|
-
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
13010
|
-
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
13011
|
-
accepted,
|
|
13012
|
-
verdicts,
|
|
13013
|
-
authors: [...authors].toSorted(),
|
|
13014
|
-
warnings
|
|
13015
|
-
};
|
|
13016
|
-
}
|
|
13017
|
-
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
13018
|
-
function activeStanding(rows, fanout, leads = null) {
|
|
13019
|
-
const active = [];
|
|
13020
|
-
for (const row of rows) {
|
|
13021
|
-
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
12522
|
+
if (syntax.rawBacktick && char === "`") {
|
|
12523
|
+
if (!scanString("`", false, true))
|
|
12524
|
+
return null;
|
|
13022
12525
|
continue;
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
12526
|
+
}
|
|
12527
|
+
if (syntax.js && char === "`") {
|
|
12528
|
+
emitVerbatim(char);
|
|
12529
|
+
frames.push({ kind: "template" });
|
|
12530
|
+
index += 1;
|
|
12531
|
+
continue;
|
|
12532
|
+
}
|
|
12533
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
12534
|
+
return null;
|
|
12535
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
12536
|
+
const lineEnd = text.indexOf(`
|
|
12537
|
+
`, index);
|
|
12538
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
12539
|
+
return null;
|
|
12540
|
+
}
|
|
12541
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
12542
|
+
let end = index + 1;
|
|
12543
|
+
let inClass = false;
|
|
12544
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
12545
|
+
if (text[end] === `
|
|
12546
|
+
`)
|
|
12547
|
+
return null;
|
|
12548
|
+
if (text[end] === "\\")
|
|
12549
|
+
end += 1;
|
|
12550
|
+
else if (text[end] === "[")
|
|
12551
|
+
inClass = true;
|
|
12552
|
+
else if (text[end] === "]")
|
|
12553
|
+
inClass = false;
|
|
12554
|
+
end += 1;
|
|
12555
|
+
}
|
|
12556
|
+
if (end >= text.length)
|
|
12557
|
+
return null;
|
|
12558
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
12559
|
+
index = end + 1;
|
|
12560
|
+
continue;
|
|
12561
|
+
}
|
|
12562
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
12563
|
+
if (char === "{")
|
|
12564
|
+
frame.braces += 1;
|
|
12565
|
+
else if (char === "}") {
|
|
12566
|
+
if (frame.braces === 0) {
|
|
12567
|
+
emitVerbatim(char);
|
|
12568
|
+
frames.pop();
|
|
12569
|
+
index += 1;
|
|
12570
|
+
continue;
|
|
12571
|
+
}
|
|
12572
|
+
frame.braces -= 1;
|
|
13034
12573
|
}
|
|
13035
12574
|
}
|
|
12575
|
+
emitCode(char);
|
|
12576
|
+
index += 1;
|
|
13036
12577
|
}
|
|
13037
|
-
|
|
13038
|
-
}
|
|
13039
|
-
var OBSERVED_ONLY = new Set([
|
|
13040
|
-
"check",
|
|
13041
|
-
"merge.unverified",
|
|
13042
|
-
"policy.judged",
|
|
13043
|
-
...PR_ROW_TYPES
|
|
13044
|
-
]);
|
|
13045
|
-
|
|
13046
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
13047
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
13048
|
-
import path14 from "node:path";
|
|
13049
|
-
// packages/workit-core/src/forge/redact.ts
|
|
13050
|
-
var KEY = String.raw`[A-Za-z0-9_.-]*?(?:token|secret|password|passwd|pwd|api[_-]?key|access[_-]?key|account[_-]?key|private[_-]?key|client[_-]?secret|credentials?|authorization|cookie|signature)[A-Za-z0-9_.-]*`;
|
|
13051
|
-
var ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*["']?)((?:bearer|basic|token|digest)\s+)?([^\s"',;&]+)`, "giu");
|
|
13052
|
-
var QUOTED_ASSIGNMENT = new RegExp(String.raw`(?<![A-Za-z0-9])(["']?${KEY}["']?\s*[:=]\s*)(["'])([^"'\n]*[^\s"'][^"'\n]*)\2`, "giu");
|
|
13053
|
-
var ANSI = new RegExp(String.raw`\u001b\[[0-9;?]*[ -/]*[@-~]|\u001b\][^\u0007]*\u0007`, "gu");
|
|
13054
|
-
|
|
13055
|
-
// packages/workit-core/src/git/ops.ts
|
|
13056
|
-
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
13057
|
-
|
|
13058
|
-
// packages/workit-core/src/core/policy/attributes.ts
|
|
13059
|
-
function patternRegex(dir, pattern) {
|
|
13060
|
-
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
12578
|
+
if (frames.length !== 1)
|
|
13061
12579
|
return null;
|
|
13062
|
-
|
|
13063
|
-
|
|
13064
|
-
|
|
13065
|
-
|
|
13066
|
-
|
|
13067
|
-
|
|
13068
|
-
|
|
13069
|
-
|
|
13070
|
-
|
|
13071
|
-
|
|
13072
|
-
source += "[^/]*";
|
|
13073
|
-
else if (char === "?")
|
|
13074
|
-
source += "[^/]";
|
|
13075
|
-
else if (char === "[") {
|
|
13076
|
-
const end = body.indexOf("]", index + 2);
|
|
13077
|
-
if (end < 0)
|
|
13078
|
-
return null;
|
|
13079
|
-
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
13080
|
-
source += `[${set}]`;
|
|
13081
|
-
index = end;
|
|
13082
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
13083
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
13084
|
-
index += 1;
|
|
13085
|
-
} else
|
|
13086
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
12580
|
+
newline();
|
|
12581
|
+
return out;
|
|
12582
|
+
}
|
|
12583
|
+
function unbalanced(body) {
|
|
12584
|
+
let depth = 0;
|
|
12585
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
12586
|
+
if (part === "{")
|
|
12587
|
+
depth += 1;
|
|
12588
|
+
else if (part === "}")
|
|
12589
|
+
depth = Math.max(0, depth - 1);
|
|
13087
12590
|
}
|
|
13088
|
-
|
|
12591
|
+
return depth > 0;
|
|
12592
|
+
}
|
|
12593
|
+
function hasOpaqueLineBreak(text) {
|
|
12594
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
12595
|
+
}
|
|
12596
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
12597
|
+
function textOf(bytes) {
|
|
12598
|
+
if (bytes.includes(0))
|
|
12599
|
+
return null;
|
|
13089
12600
|
try {
|
|
13090
|
-
return
|
|
12601
|
+
return decoder.decode(bytes);
|
|
13091
12602
|
} catch {
|
|
13092
12603
|
return null;
|
|
13093
12604
|
}
|
|
13094
12605
|
}
|
|
13095
|
-
function
|
|
13096
|
-
const
|
|
13097
|
-
|
|
13098
|
-
|
|
13099
|
-
|
|
13100
|
-
|
|
13101
|
-
|
|
13102
|
-
|
|
13103
|
-
|
|
13104
|
-
for (const attribute of attributes) {
|
|
13105
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
13106
|
-
generated = true;
|
|
13107
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
13108
|
-
generated = false;
|
|
13109
|
-
}
|
|
13110
|
-
if (generated === null)
|
|
13111
|
-
continue;
|
|
13112
|
-
const regex = patternRegex(dir, pattern);
|
|
13113
|
-
if (regex)
|
|
13114
|
-
out.push({ regex, generated });
|
|
13115
|
-
}
|
|
13116
|
-
return out;
|
|
13117
|
-
}
|
|
13118
|
-
function generatedByAttributes(files, paths) {
|
|
13119
|
-
const rules = [...files].map(([file, text]) => {
|
|
13120
|
-
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
13121
|
-
return {
|
|
13122
|
-
depth: dir ? dir.split("/").length : 0,
|
|
13123
|
-
rules: rulesOf(dir, text)
|
|
13124
|
-
};
|
|
13125
|
-
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
13126
|
-
const out = new Set;
|
|
13127
|
-
for (const file of paths) {
|
|
13128
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
13129
|
-
if (last?.generated)
|
|
13130
|
-
out.add(file);
|
|
13131
|
-
}
|
|
13132
|
-
return out;
|
|
12606
|
+
function cosmeticChange(file, before, after) {
|
|
12607
|
+
const left = textOf(before);
|
|
12608
|
+
const right = textOf(after);
|
|
12609
|
+
if (left === null || right === null)
|
|
12610
|
+
return false;
|
|
12611
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
12612
|
+
return false;
|
|
12613
|
+
const a = substanceOf(file, left);
|
|
12614
|
+
return a !== null && a === substanceOf(file, right);
|
|
13133
12615
|
}
|
|
13134
12616
|
|
|
13135
|
-
// packages/workit-core/src/core/policy/
|
|
13136
|
-
var
|
|
13137
|
-
|
|
13138
|
-
|
|
13139
|
-
|
|
13140
|
-
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
13141
|
-
ext,
|
|
13142
|
-
{ ...C_LIKE, triple: true }
|
|
13143
|
-
]),
|
|
13144
|
-
["go", { ...C_LIKE, rawBacktick: true }],
|
|
13145
|
-
...["css", "scss", "less"].map((ext) => [
|
|
13146
|
-
ext,
|
|
13147
|
-
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
13148
|
-
]),
|
|
13149
|
-
...["py", "pyi"].map((ext) => [
|
|
13150
|
-
ext,
|
|
13151
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
13152
|
-
]),
|
|
13153
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
13154
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
13155
|
-
ext,
|
|
13156
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
13157
|
-
])
|
|
13158
|
-
]);
|
|
13159
|
-
var DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
13160
|
-
var extensionOf = (file) => {
|
|
13161
|
-
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
13162
|
-
const dot = name.lastIndexOf(".");
|
|
13163
|
-
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
12617
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
12618
|
+
var LANE_THRESHOLDS = {
|
|
12619
|
+
selfMaxLines: 100,
|
|
12620
|
+
selfMaxFiles: 5,
|
|
12621
|
+
fullMinLines: 500
|
|
13164
12622
|
};
|
|
13165
|
-
var
|
|
13166
|
-
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
13170
|
-
|
|
13171
|
-
|
|
13172
|
-
|
|
13173
|
-
"
|
|
13174
|
-
"
|
|
13175
|
-
"
|
|
13176
|
-
"
|
|
13177
|
-
"
|
|
13178
|
-
"
|
|
13179
|
-
"
|
|
13180
|
-
"
|
|
13181
|
-
"
|
|
13182
|
-
"
|
|
13183
|
-
"
|
|
13184
|
-
"
|
|
13185
|
-
"
|
|
13186
|
-
"
|
|
12623
|
+
var RISK_TOKENS = new Set([
|
|
12624
|
+
"auth",
|
|
12625
|
+
"authn",
|
|
12626
|
+
"authz",
|
|
12627
|
+
"oauth",
|
|
12628
|
+
"security",
|
|
12629
|
+
"permission",
|
|
12630
|
+
"permissions",
|
|
12631
|
+
"secret",
|
|
12632
|
+
"secrets",
|
|
12633
|
+
"crypto",
|
|
12634
|
+
"gate",
|
|
12635
|
+
"gates",
|
|
12636
|
+
"policy",
|
|
12637
|
+
"policies",
|
|
12638
|
+
"hook",
|
|
12639
|
+
"hooks",
|
|
12640
|
+
"husky",
|
|
12641
|
+
"lefthook",
|
|
12642
|
+
"release",
|
|
12643
|
+
"releases",
|
|
12644
|
+
"migration",
|
|
12645
|
+
"migrations",
|
|
12646
|
+
"migrate",
|
|
12647
|
+
"workflows"
|
|
13187
12648
|
]);
|
|
13188
|
-
|
|
13189
|
-
|
|
13190
|
-
|
|
13191
|
-
|
|
13192
|
-
|
|
13193
|
-
|
|
13194
|
-
|
|
13195
|
-
|
|
12649
|
+
var RISK_FILES = new Set([
|
|
12650
|
+
".gitlab-ci.yml",
|
|
12651
|
+
"Jenkinsfile",
|
|
12652
|
+
"azure-pipelines.yml",
|
|
12653
|
+
".pre-commit-config.yaml",
|
|
12654
|
+
".workit/risk-paths"
|
|
12655
|
+
]);
|
|
12656
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
12657
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
12658
|
+
var LOCKFILES = new Set([
|
|
12659
|
+
"bun.lock",
|
|
12660
|
+
"bun.lockb",
|
|
12661
|
+
"package-lock.json",
|
|
12662
|
+
"npm-shrinkwrap.json",
|
|
12663
|
+
"yarn.lock",
|
|
12664
|
+
"pnpm-lock.yaml",
|
|
12665
|
+
"Cargo.lock",
|
|
12666
|
+
"go.sum",
|
|
12667
|
+
"poetry.lock",
|
|
12668
|
+
"uv.lock",
|
|
12669
|
+
"Pipfile.lock",
|
|
12670
|
+
"Gemfile.lock",
|
|
12671
|
+
"composer.lock"
|
|
12672
|
+
]);
|
|
12673
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
12674
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
12675
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
12676
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
12677
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
12678
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
12679
|
+
function kindOf(file) {
|
|
12680
|
+
if (file.binary)
|
|
12681
|
+
return "behavior";
|
|
12682
|
+
if (file.generated)
|
|
12683
|
+
return "generated";
|
|
12684
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
12685
|
+
return "lockfile";
|
|
12686
|
+
if (isDoc(file.path))
|
|
12687
|
+
return "doc";
|
|
12688
|
+
if (file.cosmetic)
|
|
12689
|
+
return "cosmetic";
|
|
12690
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
13196
12691
|
}
|
|
13197
|
-
|
|
13198
|
-
|
|
13199
|
-
|
|
13200
|
-
|
|
12692
|
+
var globCache = new Map;
|
|
12693
|
+
function globMatch(pattern, file) {
|
|
12694
|
+
let regex = globCache.get(pattern);
|
|
12695
|
+
if (!regex) {
|
|
12696
|
+
let source = "";
|
|
12697
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
12698
|
+
const char = pattern[index];
|
|
12699
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
12700
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
12701
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
12702
|
+
} else if (char === "*")
|
|
12703
|
+
source += "[^/]*";
|
|
12704
|
+
else if (char === "?")
|
|
12705
|
+
source += "[^/]";
|
|
12706
|
+
else
|
|
12707
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
12708
|
+
}
|
|
12709
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
12710
|
+
globCache.set(pattern, regex);
|
|
12711
|
+
}
|
|
12712
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
13201
12713
|
}
|
|
13202
|
-
function
|
|
13203
|
-
|
|
13204
|
-
|
|
13205
|
-
|
|
13206
|
-
|
|
13207
|
-
|
|
13208
|
-
|
|
13209
|
-
|
|
13210
|
-
|
|
13211
|
-
|
|
13212
|
-
`);
|
|
13213
|
-
|
|
13214
|
-
|
|
13215
|
-
|
|
12714
|
+
function isRiskPath(file, extra = []) {
|
|
12715
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
12716
|
+
return true;
|
|
12717
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
12718
|
+
return true;
|
|
12719
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
12720
|
+
return false;
|
|
12721
|
+
return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
|
|
12722
|
+
}
|
|
12723
|
+
var parseRiskPaths = (text) => text.split(`
|
|
12724
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
12725
|
+
function computeLane(input) {
|
|
12726
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
12727
|
+
const risky = new Set(riskFiles);
|
|
12728
|
+
const kinds = input.files.map((file) => ({
|
|
12729
|
+
file,
|
|
12730
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
12731
|
+
}));
|
|
12732
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
12733
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
12734
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
12735
|
+
const result = (lane, reasons) => ({
|
|
12736
|
+
lane,
|
|
12737
|
+
reasons,
|
|
12738
|
+
lines,
|
|
12739
|
+
files: behavior.length,
|
|
12740
|
+
riskFiles
|
|
12741
|
+
});
|
|
12742
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
12743
|
+
if (input.riskTier === "high")
|
|
12744
|
+
return result("full", ["the task judgment records high risk"]);
|
|
12745
|
+
if (riskFiles.length)
|
|
12746
|
+
return result("full", [
|
|
12747
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
12748
|
+
]);
|
|
12749
|
+
if (!behavior.length)
|
|
12750
|
+
return result("none", [
|
|
12751
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
12752
|
+
]);
|
|
12753
|
+
if (lines >= fullMinLines)
|
|
12754
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
12755
|
+
if (lines > selfMaxLines)
|
|
12756
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
12757
|
+
if (behavior.length > selfMaxFiles)
|
|
12758
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
12759
|
+
if (opaque.length)
|
|
12760
|
+
return result("focused", [
|
|
12761
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
12762
|
+
]);
|
|
12763
|
+
return result("self", [
|
|
12764
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
12765
|
+
]);
|
|
12766
|
+
}
|
|
12767
|
+
function untrackedStat(cwd, file) {
|
|
12768
|
+
try {
|
|
12769
|
+
const bytes = readFileSync4(path13.join(cwd, file));
|
|
12770
|
+
if (bytes.includes(0))
|
|
12771
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
12772
|
+
const text = bytes.toString("utf8");
|
|
12773
|
+
if (hasOpaqueLineBreak(text))
|
|
12774
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
12775
|
+
const lines = text.split(`
|
|
12776
|
+
`).length - (text.endsWith(`
|
|
12777
|
+
`) ? 1 : 0);
|
|
12778
|
+
return { path: file, added: lines, deleted: 0 };
|
|
12779
|
+
} catch {
|
|
12780
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13216
12781
|
}
|
|
13217
|
-
|
|
13218
|
-
|
|
13219
|
-
|
|
13220
|
-
|
|
13221
|
-
|
|
13222
|
-
|
|
13223
|
-
|
|
13224
|
-
|
|
13225
|
-
|
|
13226
|
-
|
|
13227
|
-
|
|
13228
|
-
|
|
13229
|
-
|
|
13230
|
-
const
|
|
13231
|
-
|
|
13232
|
-
|
|
13233
|
-
|
|
13234
|
-
|
|
13235
|
-
|
|
13236
|
-
|
|
13237
|
-
|
|
13238
|
-
if (out.length > lineStart) {
|
|
13239
|
-
out += `
|
|
13240
|
-
`;
|
|
13241
|
-
lineStart = out.length;
|
|
13242
|
-
}
|
|
13243
|
-
};
|
|
13244
|
-
const regexAllowed = () => {
|
|
13245
|
-
let end = out.length;
|
|
13246
|
-
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
13247
|
-
end -= 1;
|
|
13248
|
-
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
13249
|
-
return true;
|
|
13250
|
-
let start = end;
|
|
13251
|
-
while (start > 0 && WORD.test(out[start - 1]))
|
|
13252
|
-
start -= 1;
|
|
13253
|
-
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
13254
|
-
};
|
|
13255
|
-
const scanString = (quote, escapes, multiline) => {
|
|
13256
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
13257
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
13258
|
-
let end = index + quote.length;
|
|
13259
|
-
while (end < text.length) {
|
|
13260
|
-
const char = text[end];
|
|
13261
|
-
if (escapes && char === "\\") {
|
|
13262
|
-
end += 2;
|
|
13263
|
-
continue;
|
|
13264
|
-
}
|
|
13265
|
-
if (text.startsWith(quote, end)) {
|
|
13266
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
13267
|
-
return false;
|
|
13268
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
13269
|
-
index = end + quote.length;
|
|
13270
|
-
return true;
|
|
12782
|
+
}
|
|
12783
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
12784
|
+
var ABSENT = /^0+$/u;
|
|
12785
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
12786
|
+
const raw = diffRaw(cwd, fork, head);
|
|
12787
|
+
if (!raw)
|
|
12788
|
+
return null;
|
|
12789
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
12790
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
12791
|
+
if (!blobs)
|
|
12792
|
+
return null;
|
|
12793
|
+
const out = new Set;
|
|
12794
|
+
const opaque = new Set;
|
|
12795
|
+
for (const entry of candidates) {
|
|
12796
|
+
const before = blobs.get(entry.oldBlob);
|
|
12797
|
+
let after = blobs.get(entry.newBlob);
|
|
12798
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
12799
|
+
try {
|
|
12800
|
+
after = readFileSync4(path13.join(cwd, entry.path));
|
|
12801
|
+
} catch {
|
|
12802
|
+
after = undefined;
|
|
13271
12803
|
}
|
|
13272
|
-
|
|
13273
|
-
|
|
13274
|
-
|
|
13275
|
-
|
|
13276
|
-
}
|
|
13277
|
-
return false;
|
|
13278
|
-
};
|
|
13279
|
-
while (index < text.length) {
|
|
13280
|
-
const frame = frames[frames.length - 1];
|
|
13281
|
-
const char = text[index];
|
|
13282
|
-
if (frame.kind === "template") {
|
|
13283
|
-
if (char === "\\") {
|
|
13284
|
-
emitVerbatim(text.slice(index, index + 2));
|
|
13285
|
-
index += 2;
|
|
13286
|
-
} else if (char === "`") {
|
|
13287
|
-
emitVerbatim(char);
|
|
13288
|
-
frames.pop();
|
|
13289
|
-
index += 1;
|
|
13290
|
-
} else if (text.startsWith("${", index)) {
|
|
13291
|
-
emitVerbatim("${");
|
|
13292
|
-
frames.push({ kind: "code", braces: 0 });
|
|
13293
|
-
index += 2;
|
|
13294
|
-
} else {
|
|
13295
|
-
out += char;
|
|
13296
|
-
kept = out.length;
|
|
13297
|
-
index += 1;
|
|
13298
|
-
}
|
|
13299
|
-
continue;
|
|
13300
|
-
}
|
|
13301
|
-
if (char === `
|
|
13302
|
-
`) {
|
|
13303
|
-
newline();
|
|
13304
|
-
index += 1;
|
|
13305
|
-
continue;
|
|
13306
|
-
}
|
|
13307
|
-
if (SPACE.test(char)) {
|
|
13308
|
-
if (collapse)
|
|
13309
|
-
pendingSpace = true;
|
|
13310
|
-
else
|
|
13311
|
-
out += char;
|
|
13312
|
-
index += 1;
|
|
13313
|
-
continue;
|
|
13314
|
-
}
|
|
13315
|
-
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
13316
|
-
if (lineOpener) {
|
|
13317
|
-
const end = text.indexOf(`
|
|
13318
|
-
`, index);
|
|
13319
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
13320
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
13321
|
-
emitVerbatim(comment);
|
|
13322
|
-
else
|
|
13323
|
-
pendingSpace = true;
|
|
13324
|
-
index = end < 0 ? text.length : end;
|
|
13325
|
-
continue;
|
|
13326
|
-
}
|
|
13327
|
-
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
13328
|
-
if (block) {
|
|
13329
|
-
const end = text.indexOf(block[1], index + block[0].length);
|
|
13330
|
-
if (end < 0)
|
|
13331
|
-
return null;
|
|
13332
|
-
const comment = text.slice(index, end + block[1].length);
|
|
13333
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
13334
|
-
emitVerbatim(comment);
|
|
13335
|
-
else
|
|
13336
|
-
pendingSpace = true;
|
|
13337
|
-
if (comment.includes(`
|
|
13338
|
-
`))
|
|
13339
|
-
newline();
|
|
13340
|
-
index = end + block[1].length;
|
|
13341
|
-
continue;
|
|
13342
|
-
}
|
|
13343
|
-
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
13344
|
-
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
13345
|
-
return null;
|
|
13346
|
-
continue;
|
|
13347
|
-
}
|
|
13348
|
-
if (syntax.quotes.includes(char)) {
|
|
13349
|
-
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
13350
|
-
return null;
|
|
13351
|
-
continue;
|
|
13352
|
-
}
|
|
13353
|
-
if (syntax.literal?.includes(char)) {
|
|
13354
|
-
if (!scanString(char, false, false))
|
|
13355
|
-
return null;
|
|
13356
|
-
continue;
|
|
13357
|
-
}
|
|
13358
|
-
if (syntax.rawBacktick && char === "`") {
|
|
13359
|
-
if (!scanString("`", false, true))
|
|
13360
|
-
return null;
|
|
13361
|
-
continue;
|
|
13362
|
-
}
|
|
13363
|
-
if (syntax.js && char === "`") {
|
|
13364
|
-
emitVerbatim(char);
|
|
13365
|
-
frames.push({ kind: "template" });
|
|
13366
|
-
index += 1;
|
|
13367
|
-
continue;
|
|
13368
|
-
}
|
|
13369
|
-
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
13370
|
-
return null;
|
|
13371
|
-
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
13372
|
-
const lineEnd = text.indexOf(`
|
|
13373
|
-
`, index);
|
|
13374
|
-
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
13375
|
-
return null;
|
|
13376
|
-
}
|
|
13377
|
-
if (syntax.js && char === "/" && regexAllowed()) {
|
|
13378
|
-
let end = index + 1;
|
|
13379
|
-
let inClass = false;
|
|
13380
|
-
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
13381
|
-
if (text[end] === `
|
|
13382
|
-
`)
|
|
13383
|
-
return null;
|
|
13384
|
-
if (text[end] === "\\")
|
|
13385
|
-
end += 1;
|
|
13386
|
-
else if (text[end] === "[")
|
|
13387
|
-
inClass = true;
|
|
13388
|
-
else if (text[end] === "]")
|
|
13389
|
-
inClass = false;
|
|
13390
|
-
end += 1;
|
|
13391
|
-
}
|
|
13392
|
-
if (end >= text.length)
|
|
13393
|
-
return null;
|
|
13394
|
-
emitVerbatim(text.slice(index, end + 1));
|
|
13395
|
-
index = end + 1;
|
|
13396
|
-
continue;
|
|
13397
|
-
}
|
|
13398
|
-
if (frames.length > 1 && frame.kind === "code") {
|
|
13399
|
-
if (char === "{")
|
|
13400
|
-
frame.braces += 1;
|
|
13401
|
-
else if (char === "}") {
|
|
13402
|
-
if (frame.braces === 0) {
|
|
13403
|
-
emitVerbatim(char);
|
|
13404
|
-
frames.pop();
|
|
13405
|
-
index += 1;
|
|
13406
|
-
continue;
|
|
13407
|
-
}
|
|
13408
|
-
frame.braces -= 1;
|
|
13409
|
-
}
|
|
13410
|
-
}
|
|
13411
|
-
emitCode(char);
|
|
13412
|
-
index += 1;
|
|
12804
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
12805
|
+
out.add(entry.path);
|
|
12806
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
12807
|
+
opaque.add(entry.path);
|
|
13413
12808
|
}
|
|
13414
|
-
|
|
13415
|
-
return null;
|
|
13416
|
-
newline();
|
|
13417
|
-
return out;
|
|
12809
|
+
return { cosmetic: out, opaque };
|
|
13418
12810
|
}
|
|
13419
|
-
function
|
|
13420
|
-
|
|
13421
|
-
|
|
13422
|
-
|
|
13423
|
-
|
|
13424
|
-
|
|
13425
|
-
|
|
12811
|
+
function generatedAt(cwd, fork, paths) {
|
|
12812
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
12813
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
12814
|
+
if (!files || !blobs)
|
|
12815
|
+
return new Set;
|
|
12816
|
+
const texts = new Map;
|
|
12817
|
+
for (const [file, id] of files) {
|
|
12818
|
+
const bytes = blobs.get(id);
|
|
12819
|
+
if (bytes)
|
|
12820
|
+
texts.set(file, bytes.toString("utf8"));
|
|
13426
12821
|
}
|
|
13427
|
-
return
|
|
13428
|
-
}
|
|
13429
|
-
function hasOpaqueLineBreak(text) {
|
|
13430
|
-
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
12822
|
+
return generatedByAttributes(texts, paths);
|
|
13431
12823
|
}
|
|
13432
|
-
var
|
|
13433
|
-
function
|
|
13434
|
-
|
|
12824
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
12825
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
12826
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
12827
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
12828
|
+
if (!stats || !cosmetic)
|
|
13435
12829
|
return null;
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
12830
|
+
const files = [
|
|
12831
|
+
...stats.map((entry) => ({
|
|
12832
|
+
...entry,
|
|
12833
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
12834
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
12835
|
+
})),
|
|
12836
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
12837
|
+
];
|
|
12838
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
12839
|
+
return files.map((entry) => ({
|
|
12840
|
+
...entry,
|
|
12841
|
+
generated: generated.has(entry.path)
|
|
12842
|
+
}));
|
|
12843
|
+
}
|
|
12844
|
+
function driftFiles(cwd, range) {
|
|
12845
|
+
if (range.fromFork === range.toFork)
|
|
12846
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
12847
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
12848
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
12849
|
+
if (!drifted || !stats)
|
|
13439
12850
|
return null;
|
|
12851
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
12852
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
12853
|
+
return drifted.map((file) => ({
|
|
12854
|
+
path: file,
|
|
12855
|
+
added: byPath.get(file)?.added ?? 0,
|
|
12856
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
12857
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
12858
|
+
cosmetic: false,
|
|
12859
|
+
generated: generated.has(file)
|
|
12860
|
+
}));
|
|
12861
|
+
}
|
|
12862
|
+
function branchLane(cwd, options) {
|
|
12863
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
12864
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
12865
|
+
const head = options.workingTree ? null : options.head;
|
|
12866
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
12867
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
12868
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
12869
|
+
if (!fork || !files) {
|
|
12870
|
+
const high = riskTier === "high";
|
|
12871
|
+
return {
|
|
12872
|
+
lane: high ? "full" : "focused",
|
|
12873
|
+
reasons: [
|
|
12874
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
12875
|
+
...high ? ["the task judgment records high risk"] : []
|
|
12876
|
+
],
|
|
12877
|
+
lines: 0,
|
|
12878
|
+
files: 0,
|
|
12879
|
+
riskFiles: [],
|
|
12880
|
+
base,
|
|
12881
|
+
head: options.head
|
|
12882
|
+
};
|
|
13440
12883
|
}
|
|
12884
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
12885
|
+
return {
|
|
12886
|
+
...computeLane({
|
|
12887
|
+
files,
|
|
12888
|
+
riskTier,
|
|
12889
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
12890
|
+
}),
|
|
12891
|
+
base,
|
|
12892
|
+
head: options.head
|
|
12893
|
+
};
|
|
13441
12894
|
}
|
|
13442
|
-
|
|
13443
|
-
|
|
13444
|
-
|
|
13445
|
-
|
|
13446
|
-
|
|
13447
|
-
|
|
13448
|
-
|
|
13449
|
-
|
|
13450
|
-
|
|
12895
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
12896
|
+
|
|
12897
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
12898
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
12899
|
+
function verdictDrift(cwd, input) {
|
|
12900
|
+
const since = input.from;
|
|
12901
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
12902
|
+
carries: false,
|
|
12903
|
+
since,
|
|
12904
|
+
files,
|
|
12905
|
+
focused,
|
|
12906
|
+
reason
|
|
12907
|
+
});
|
|
12908
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
12909
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
12910
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
12911
|
+
if (!fromFork || !toFork || !files)
|
|
12912
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
12913
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
12914
|
+
const lane = computeLane({ files, riskPaths });
|
|
12915
|
+
const paths = files.map((file) => file.path);
|
|
12916
|
+
if (lane.lane === "none")
|
|
12917
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
12918
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
12919
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
12920
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
13451
12921
|
}
|
|
13452
12922
|
|
|
13453
|
-
// packages/workit-core/src/
|
|
13454
|
-
var
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
var
|
|
13460
|
-
"
|
|
13461
|
-
"
|
|
13462
|
-
"
|
|
13463
|
-
"
|
|
13464
|
-
"security",
|
|
13465
|
-
"permission",
|
|
13466
|
-
"permissions",
|
|
13467
|
-
"secret",
|
|
13468
|
-
"secrets",
|
|
13469
|
-
"crypto",
|
|
13470
|
-
"gate",
|
|
13471
|
-
"gates",
|
|
13472
|
-
"policy",
|
|
13473
|
-
"policies",
|
|
13474
|
-
"hook",
|
|
13475
|
-
"hooks",
|
|
13476
|
-
"husky",
|
|
13477
|
-
"lefthook",
|
|
13478
|
-
"release",
|
|
13479
|
-
"releases",
|
|
13480
|
-
"migration",
|
|
13481
|
-
"migrations",
|
|
13482
|
-
"migrate",
|
|
13483
|
-
"workflows"
|
|
13484
|
-
]);
|
|
13485
|
-
var RISK_FILES = new Set([
|
|
13486
|
-
".gitlab-ci.yml",
|
|
13487
|
-
"Jenkinsfile",
|
|
13488
|
-
"azure-pipelines.yml",
|
|
13489
|
-
".pre-commit-config.yaml",
|
|
13490
|
-
".workit/risk-paths"
|
|
13491
|
-
]);
|
|
13492
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
13493
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
13494
|
-
var LOCKFILES = new Set([
|
|
13495
|
-
"bun.lock",
|
|
13496
|
-
"bun.lockb",
|
|
13497
|
-
"package-lock.json",
|
|
13498
|
-
"npm-shrinkwrap.json",
|
|
13499
|
-
"yarn.lock",
|
|
13500
|
-
"pnpm-lock.yaml",
|
|
13501
|
-
"Cargo.lock",
|
|
13502
|
-
"go.sum",
|
|
13503
|
-
"poetry.lock",
|
|
13504
|
-
"uv.lock",
|
|
13505
|
-
"Pipfile.lock",
|
|
13506
|
-
"Gemfile.lock",
|
|
13507
|
-
"composer.lock"
|
|
12923
|
+
// packages/workit-core/src/ledger.ts
|
|
12924
|
+
var LEDGER_VERSION = 1;
|
|
12925
|
+
var MAX_LINE_BYTES = 4096;
|
|
12926
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
12927
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
12928
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
12929
|
+
var PR_ROW_TYPES = new Set([
|
|
12930
|
+
"pr.created",
|
|
12931
|
+
"pr.status",
|
|
12932
|
+
"pr.merged",
|
|
12933
|
+
"pr.linked"
|
|
13508
12934
|
]);
|
|
13509
|
-
var
|
|
13510
|
-
|
|
13511
|
-
|
|
13512
|
-
|
|
13513
|
-
|
|
13514
|
-
|
|
13515
|
-
|
|
13516
|
-
|
|
13517
|
-
|
|
13518
|
-
|
|
13519
|
-
|
|
13520
|
-
|
|
13521
|
-
|
|
13522
|
-
|
|
13523
|
-
|
|
13524
|
-
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
function globMatch(pattern, file) {
|
|
13530
|
-
let regex = globCache.get(pattern);
|
|
13531
|
-
if (!regex) {
|
|
13532
|
-
let source = "";
|
|
13533
|
-
for (let index = 0;index < pattern.length; index += 1) {
|
|
13534
|
-
const char = pattern[index];
|
|
13535
|
-
if (char === "*" && pattern[index + 1] === "*") {
|
|
13536
|
-
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
13537
|
-
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
13538
|
-
} else if (char === "*")
|
|
13539
|
-
source += "[^/]*";
|
|
13540
|
-
else if (char === "?")
|
|
13541
|
-
source += "[^/]";
|
|
13542
|
-
else
|
|
13543
|
-
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
12935
|
+
var err = (code, error, unblock) => ({
|
|
12936
|
+
ok: false,
|
|
12937
|
+
code,
|
|
12938
|
+
error,
|
|
12939
|
+
...unblock ? { unblock } : {}
|
|
12940
|
+
});
|
|
12941
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
12942
|
+
const run = spawnSync8("git", args, {
|
|
12943
|
+
cwd,
|
|
12944
|
+
encoding: "utf8",
|
|
12945
|
+
timeout: timeoutMs,
|
|
12946
|
+
killSignal: "SIGKILL",
|
|
12947
|
+
windowsHide: true,
|
|
12948
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
12949
|
+
env: {
|
|
12950
|
+
...process.env,
|
|
12951
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
12952
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
12953
|
+
LC_ALL: "C",
|
|
12954
|
+
LANGUAGE: "C"
|
|
13544
12955
|
}
|
|
13545
|
-
|
|
13546
|
-
|
|
12956
|
+
});
|
|
12957
|
+
return {
|
|
12958
|
+
ok: run.status === 0,
|
|
12959
|
+
stdout: run.stdout ?? "",
|
|
12960
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
12961
|
+
};
|
|
12962
|
+
};
|
|
12963
|
+
var git3 = (cwd, args) => {
|
|
12964
|
+
const run = gitRun2(cwd, args);
|
|
12965
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
12966
|
+
return value ? value : null;
|
|
12967
|
+
};
|
|
12968
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
12969
|
+
function resolveCommit(cwd, ref) {
|
|
12970
|
+
if (!safeRef2(ref))
|
|
12971
|
+
return null;
|
|
12972
|
+
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
12973
|
+
}
|
|
12974
|
+
function branchHead(cwd, branch) {
|
|
12975
|
+
if (!safeRef2(branch))
|
|
12976
|
+
return null;
|
|
12977
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
12978
|
+
}
|
|
12979
|
+
function diffHash(cwd, base, head) {
|
|
12980
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
12981
|
+
return null;
|
|
12982
|
+
const diff = gitRun2(cwd, [
|
|
12983
|
+
"diff",
|
|
12984
|
+
"-U3",
|
|
12985
|
+
"--diff-algorithm=myers",
|
|
12986
|
+
"--indent-heuristic",
|
|
12987
|
+
"--no-color",
|
|
12988
|
+
"--no-ext-diff",
|
|
12989
|
+
"--no-textconv",
|
|
12990
|
+
"--no-renames",
|
|
12991
|
+
"--full-index",
|
|
12992
|
+
"--binary",
|
|
12993
|
+
"--src-prefix=a/",
|
|
12994
|
+
"--dst-prefix=b/",
|
|
12995
|
+
`${base}...${head}`,
|
|
12996
|
+
"--"
|
|
12997
|
+
], GIT_TIMEOUTS.worktree);
|
|
12998
|
+
if (!diff.ok || !diff.stdout)
|
|
12999
|
+
return null;
|
|
13000
|
+
const hash = createHash10("sha256");
|
|
13001
|
+
for (const line of diff.stdout.split(`
|
|
13002
|
+
`)) {
|
|
13003
|
+
if (line.startsWith("index "))
|
|
13004
|
+
continue;
|
|
13005
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
13006
|
+
hash.update(`
|
|
13007
|
+
`);
|
|
13547
13008
|
}
|
|
13548
|
-
return
|
|
13009
|
+
return `sha256:${hash.digest("hex")}`;
|
|
13549
13010
|
}
|
|
13550
|
-
function
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13011
|
+
function storeRoot(cwd) {
|
|
13012
|
+
const location = resolveStore(path14.resolve(cwd));
|
|
13013
|
+
if (location instanceof Error)
|
|
13014
|
+
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");
|
|
13015
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
13016
|
+
}
|
|
13017
|
+
function ledgerPath(cwd) {
|
|
13018
|
+
const root = storeRoot(cwd);
|
|
13019
|
+
return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13020
|
+
}
|
|
13021
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
13022
|
+
var NETWORK_FS_TYPES = new Set([
|
|
13023
|
+
26985,
|
|
13024
|
+
20859,
|
|
13025
|
+
4266872130,
|
|
13026
|
+
4283649346,
|
|
13027
|
+
1702057286,
|
|
13028
|
+
1397113167,
|
|
13029
|
+
12805120,
|
|
13030
|
+
16914839,
|
|
13031
|
+
1196443219,
|
|
13032
|
+
198183888
|
|
13033
|
+
]);
|
|
13034
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
13035
|
+
var onNetworkFs = (dir) => {
|
|
13036
|
+
if (process.platform !== "linux")
|
|
13037
|
+
return false;
|
|
13038
|
+
try {
|
|
13039
|
+
return isNetworkFsType(fs11.statfsSync(dir).type);
|
|
13040
|
+
} catch {
|
|
13041
|
+
return false;
|
|
13042
|
+
}
|
|
13043
|
+
};
|
|
13044
|
+
var LOCK_WAIT_MS = 5000;
|
|
13045
|
+
var LOCK_STALE_MS = 30000;
|
|
13046
|
+
var LOCK_OWNER = "owner";
|
|
13047
|
+
var lockToken = (lock) => {
|
|
13048
|
+
try {
|
|
13049
|
+
return fs11.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
|
|
13050
|
+
} catch {
|
|
13051
|
+
return null;
|
|
13052
|
+
}
|
|
13053
|
+
};
|
|
13054
|
+
var lockAgeMs = (lock) => {
|
|
13055
|
+
for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
|
|
13056
|
+
try {
|
|
13057
|
+
return Date.now() - fs11.statSync(target).mtimeMs;
|
|
13058
|
+
} catch {}
|
|
13059
|
+
}
|
|
13060
|
+
return null;
|
|
13061
|
+
};
|
|
13062
|
+
var ledgerLock = {
|
|
13063
|
+
acquire(lock) {
|
|
13064
|
+
try {
|
|
13065
|
+
fs11.mkdirSync(lock);
|
|
13066
|
+
} catch (error) {
|
|
13067
|
+
if (error.code === "EEXIST")
|
|
13068
|
+
return null;
|
|
13069
|
+
throw error;
|
|
13070
|
+
}
|
|
13071
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
13072
|
+
fs11.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
13073
|
+
return token;
|
|
13074
|
+
},
|
|
13075
|
+
release(lock, token) {
|
|
13076
|
+
if (lockToken(lock) !== token)
|
|
13077
|
+
return false;
|
|
13078
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
13079
|
+
try {
|
|
13080
|
+
fs11.renameSync(lock, tomb);
|
|
13081
|
+
} catch {
|
|
13082
|
+
return false;
|
|
13083
|
+
}
|
|
13084
|
+
if (lockToken(tomb) !== token) {
|
|
13085
|
+
try {
|
|
13086
|
+
fs11.renameSync(tomb, lock);
|
|
13087
|
+
} catch {}
|
|
13088
|
+
return false;
|
|
13089
|
+
}
|
|
13090
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13554
13091
|
return true;
|
|
13555
|
-
|
|
13092
|
+
},
|
|
13093
|
+
token: lockToken,
|
|
13094
|
+
reap(lock, observed) {
|
|
13095
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13096
|
+
try {
|
|
13097
|
+
fs11.renameSync(lock, tomb);
|
|
13098
|
+
} catch {
|
|
13099
|
+
return false;
|
|
13100
|
+
}
|
|
13101
|
+
if (lockToken(tomb) === observed) {
|
|
13102
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13103
|
+
return true;
|
|
13104
|
+
}
|
|
13105
|
+
try {
|
|
13106
|
+
fs11.renameSync(tomb, lock);
|
|
13107
|
+
} catch {
|
|
13108
|
+
fs11.rmSync(tomb, { recursive: true, force: true });
|
|
13109
|
+
}
|
|
13556
13110
|
return false;
|
|
13557
|
-
|
|
13111
|
+
}
|
|
13112
|
+
};
|
|
13113
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
13114
|
+
const lock = `${file}.lock`;
|
|
13115
|
+
const deadline = Date.now() + waitMs;
|
|
13116
|
+
let token = null;
|
|
13117
|
+
for (;; ) {
|
|
13118
|
+
token = ledgerLock.acquire(lock);
|
|
13119
|
+
if (token)
|
|
13120
|
+
break;
|
|
13121
|
+
const observed = lockToken(lock);
|
|
13122
|
+
const age = lockAgeMs(lock);
|
|
13123
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
13124
|
+
continue;
|
|
13125
|
+
if (Date.now() >= deadline)
|
|
13126
|
+
return null;
|
|
13127
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
13128
|
+
}
|
|
13129
|
+
try {
|
|
13130
|
+
return fn();
|
|
13131
|
+
} finally {
|
|
13132
|
+
ledgerLock.release(lock, token);
|
|
13133
|
+
}
|
|
13558
13134
|
}
|
|
13559
|
-
var
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13575
|
-
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13583
|
-
|
|
13584
|
-
|
|
13585
|
-
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
|
|
13600
|
-
|
|
13601
|
-
|
|
13135
|
+
var sha2562 = (text) => createHash10("sha256").update(text).digest("hex");
|
|
13136
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
13137
|
+
${body}`);
|
|
13138
|
+
var TAIL_BYTES = 64 * 1024;
|
|
13139
|
+
var lastLine = (fd, size) => {
|
|
13140
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
13141
|
+
const buffer = Buffer.alloc(span);
|
|
13142
|
+
fs11.readSync(fd, buffer, 0, span, size - span);
|
|
13143
|
+
const end = buffer.lastIndexOf(10);
|
|
13144
|
+
if (end < 0) {
|
|
13145
|
+
if (span === size)
|
|
13146
|
+
return null;
|
|
13147
|
+
continue;
|
|
13148
|
+
}
|
|
13149
|
+
let stop = end;
|
|
13150
|
+
let text = "";
|
|
13151
|
+
while (stop >= 0) {
|
|
13152
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
13153
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
13154
|
+
if (text) {
|
|
13155
|
+
if (start >= 0 || span === size)
|
|
13156
|
+
return text;
|
|
13157
|
+
break;
|
|
13158
|
+
}
|
|
13159
|
+
if (start < 0) {
|
|
13160
|
+
if (span === size)
|
|
13161
|
+
return null;
|
|
13162
|
+
break;
|
|
13163
|
+
}
|
|
13164
|
+
stop = start;
|
|
13165
|
+
}
|
|
13166
|
+
if (span === size)
|
|
13167
|
+
return text || null;
|
|
13168
|
+
}
|
|
13169
|
+
return null;
|
|
13170
|
+
};
|
|
13171
|
+
var tighten = (fd) => {
|
|
13172
|
+
try {
|
|
13173
|
+
if ((fs11.fstatSync(fd).mode & 63) !== 0)
|
|
13174
|
+
fs11.fchmodSync(fd, 384);
|
|
13175
|
+
} catch {}
|
|
13176
|
+
};
|
|
13177
|
+
function appendLine(file, build, options) {
|
|
13178
|
+
const write = () => {
|
|
13179
|
+
const fd = fs11.openSync(file, "a+", 384);
|
|
13180
|
+
try {
|
|
13181
|
+
if (process.platform !== "win32")
|
|
13182
|
+
tighten(fd);
|
|
13183
|
+
const size = fs11.fstatSync(fd).size;
|
|
13184
|
+
let fence = "";
|
|
13185
|
+
if (size > 0) {
|
|
13186
|
+
const last = Buffer.alloc(1);
|
|
13187
|
+
fs11.readSync(fd, last, 0, 1, size - 1);
|
|
13188
|
+
if (last[0] !== 10)
|
|
13189
|
+
fence = `
|
|
13190
|
+
`;
|
|
13191
|
+
}
|
|
13192
|
+
const line = build(lastLine(fd, size));
|
|
13193
|
+
if (!line.ok)
|
|
13194
|
+
return line;
|
|
13195
|
+
const bytes = Buffer.from(fence + line.value);
|
|
13196
|
+
const written = fs11.writeSync(fd, bytes);
|
|
13197
|
+
if (written !== bytes.length)
|
|
13198
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
13199
|
+
return { ok: true, value: undefined };
|
|
13200
|
+
} finally {
|
|
13201
|
+
fs11.closeSync(fd);
|
|
13202
|
+
}
|
|
13203
|
+
};
|
|
13204
|
+
try {
|
|
13205
|
+
fs11.mkdirSync(path14.dirname(file), { recursive: true });
|
|
13206
|
+
if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
|
|
13207
|
+
return write();
|
|
13208
|
+
const locked = withLedgerLock(file, write);
|
|
13209
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
13210
|
+
} catch (error) {
|
|
13211
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
13212
|
+
}
|
|
13213
|
+
}
|
|
13214
|
+
var sealed = (row, id, at, previous) => {
|
|
13215
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
13216
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
13217
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
13218
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
13219
|
+
return { full, line: `${JSON.stringify(full)}
|
|
13220
|
+
` };
|
|
13221
|
+
};
|
|
13222
|
+
function appendRaw(cwd, row, options) {
|
|
13223
|
+
const file = ledgerPath(cwd);
|
|
13224
|
+
if (!file.ok)
|
|
13225
|
+
return file;
|
|
13226
|
+
const id = newId2();
|
|
13227
|
+
const at = (options.now ?? new Date).toISOString();
|
|
13228
|
+
let full = null;
|
|
13229
|
+
const written = appendLine(file.value, (previous) => {
|
|
13230
|
+
const next = sealed(row, id, at, previous);
|
|
13231
|
+
const bytes = Buffer.byteLength(next.line);
|
|
13232
|
+
if (bytes > MAX_LINE_BYTES)
|
|
13233
|
+
return err("invalid_input", `ledger row is ${bytes} bytes; the limit is ${MAX_LINE_BYTES}. Shorten the text and pass details by --ref <path|url>`);
|
|
13234
|
+
full = next.full;
|
|
13235
|
+
return { ok: true, value: next.line };
|
|
13236
|
+
}, options);
|
|
13237
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
13238
|
+
}
|
|
13239
|
+
function observedRowBytes(row) {
|
|
13240
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
13241
|
+
return Buffer.byteLength(sample.line);
|
|
13242
|
+
}
|
|
13243
|
+
function appendObserved(cwd, row, options = {}) {
|
|
13244
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
13245
|
+
}
|
|
13246
|
+
function appendHookObserved(cwd, row) {
|
|
13247
|
+
return appendRaw(cwd, { ...row, observer: "host_hook" }, {});
|
|
13248
|
+
}
|
|
13249
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13250
|
+
var str = (value) => typeof value === "string" ? value : null;
|
|
13251
|
+
var normalizeActor = (value) => {
|
|
13252
|
+
const actor = isRecord2(value) ? value : {};
|
|
13253
|
+
return {
|
|
13254
|
+
host: str(actor.host) ?? "unknown",
|
|
13255
|
+
session: str(actor.session),
|
|
13256
|
+
agentId: str(actor.agentId)
|
|
13257
|
+
};
|
|
13258
|
+
};
|
|
13259
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
13260
|
+
if (by.type !== target.type)
|
|
13261
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
13262
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
13263
|
+
return "only the session that wrote a row can supersede it";
|
|
13264
|
+
if (!onBranch(target))
|
|
13265
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
13266
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
13267
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
13268
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
13269
|
+
return "a self verdict cannot replace an independent verdict";
|
|
13270
|
+
return null;
|
|
13271
|
+
}
|
|
13272
|
+
function readLedger(cwd, options = {}) {
|
|
13273
|
+
const file = ledgerPath(cwd);
|
|
13274
|
+
if (!file.ok)
|
|
13275
|
+
return file;
|
|
13276
|
+
let text = "";
|
|
13277
|
+
let stat = null;
|
|
13278
|
+
try {
|
|
13279
|
+
stat = fs11.statSync(file.value);
|
|
13280
|
+
text = fs11.readFileSync(file.value, "utf8");
|
|
13281
|
+
} catch (error) {
|
|
13282
|
+
if (error.code === "ENOENT")
|
|
13283
|
+
return {
|
|
13284
|
+
ok: true,
|
|
13285
|
+
value: {
|
|
13286
|
+
path: file.value,
|
|
13287
|
+
rows: [],
|
|
13288
|
+
skipped: 0,
|
|
13289
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
13290
|
+
}
|
|
13291
|
+
};
|
|
13292
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
13293
|
+
}
|
|
13294
|
+
const rows = [];
|
|
13295
|
+
let skipped = 0;
|
|
13296
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
13297
|
+
const earlier = new Set([null]);
|
|
13298
|
+
let chainStarted = false;
|
|
13299
|
+
const ids = new Set;
|
|
13300
|
+
for (const raw of text.split(`
|
|
13301
|
+
`)) {
|
|
13302
|
+
const trimmed = raw.trim();
|
|
13303
|
+
if (!trimmed)
|
|
13304
|
+
continue;
|
|
13305
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
13306
|
+
if (options.integrity)
|
|
13307
|
+
earlier.add(sha2562(trimmed));
|
|
13308
|
+
let parsed;
|
|
13309
|
+
try {
|
|
13310
|
+
parsed = JSON.parse(trimmed);
|
|
13311
|
+
} catch {
|
|
13312
|
+
skipped += 1;
|
|
13313
|
+
continue;
|
|
13314
|
+
}
|
|
13315
|
+
if (!isRecord2(parsed) || !str(parsed.id) || !str(parsed.type) || !str(parsed.at)) {
|
|
13316
|
+
skipped += 1;
|
|
13317
|
+
continue;
|
|
13318
|
+
}
|
|
13319
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
13320
|
+
let reason = null;
|
|
13321
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
13322
|
+
chainStarted = true;
|
|
13323
|
+
const { rowHash, ...body } = parsed;
|
|
13324
|
+
const prevHash = str(parsed.prevHash);
|
|
13325
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
13326
|
+
reason = "hash_mismatch";
|
|
13327
|
+
else if (!linked(prevHash))
|
|
13328
|
+
reason = "broken_link";
|
|
13329
|
+
} else if (chainStarted)
|
|
13330
|
+
reason = "unsigned";
|
|
13331
|
+
else
|
|
13332
|
+
rowIntegrity = "legacy";
|
|
13333
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
13334
|
+
reason = "duplicate_id";
|
|
13335
|
+
if (options.integrity)
|
|
13336
|
+
ids.add(parsed.id);
|
|
13337
|
+
if (reason)
|
|
13338
|
+
rowIntegrity = "unverified";
|
|
13339
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
13340
|
+
const row = {
|
|
13341
|
+
...parsed,
|
|
13342
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
13343
|
+
id: parsed.id,
|
|
13344
|
+
at: parsed.at,
|
|
13345
|
+
type: parsed.type,
|
|
13346
|
+
actor: normalizeActor(parsed.actor),
|
|
13347
|
+
branch: str(parsed.branch),
|
|
13348
|
+
head: str(parsed.head),
|
|
13349
|
+
base: str(parsed.base),
|
|
13350
|
+
baseSha: str(parsed.baseSha),
|
|
13351
|
+
patchId: str(parsed.patchId),
|
|
13352
|
+
diffHash: str(parsed.diffHash),
|
|
13353
|
+
tree: str(parsed.tree),
|
|
13354
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
13355
|
+
...pr === undefined ? {} : { pr },
|
|
13356
|
+
...str(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
13357
|
+
seq: rows.length + 1,
|
|
13358
|
+
superseded: false,
|
|
13359
|
+
supersedeIgnored: false,
|
|
13360
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
13361
|
+
...reason ? { integrityReason: reason } : {}
|
|
13362
|
+
};
|
|
13363
|
+
rows.push(row);
|
|
13364
|
+
if (!options.integrity)
|
|
13365
|
+
continue;
|
|
13366
|
+
if (rowIntegrity === "verified")
|
|
13367
|
+
integrity.chained += 1;
|
|
13368
|
+
else if (rowIntegrity === "legacy")
|
|
13369
|
+
integrity.legacy += 1;
|
|
13370
|
+
else if (reason)
|
|
13371
|
+
integrity.unverified.push({
|
|
13372
|
+
seq: row.seq,
|
|
13373
|
+
id: row.id,
|
|
13374
|
+
type: row.type,
|
|
13375
|
+
branch: row.branch,
|
|
13376
|
+
reason
|
|
13377
|
+
});
|
|
13378
|
+
}
|
|
13379
|
+
const scopes = new Map;
|
|
13380
|
+
const scopeOf = (branch) => {
|
|
13381
|
+
let scope = scopes.get(branch);
|
|
13382
|
+
if (!scope)
|
|
13383
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
13384
|
+
return scope;
|
|
13385
|
+
};
|
|
13386
|
+
const byId = new Map;
|
|
13387
|
+
for (const row of rows) {
|
|
13388
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
13389
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
13390
|
+
target.superseded = true;
|
|
13391
|
+
else if (row.supersedes)
|
|
13392
|
+
row.supersedeIgnored = true;
|
|
13393
|
+
if (!byId.has(row.id))
|
|
13394
|
+
byId.set(row.id, row);
|
|
13395
|
+
}
|
|
13396
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
13397
|
+
tamperCache.set(file.value, {
|
|
13398
|
+
size: stat.size,
|
|
13399
|
+
stamp: fileStamp(stat),
|
|
13400
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
13401
|
+
});
|
|
13402
|
+
return {
|
|
13403
|
+
ok: true,
|
|
13404
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
13405
|
+
};
|
|
13602
13406
|
}
|
|
13603
|
-
|
|
13407
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
13408
|
+
var tamperCache = new Map;
|
|
13409
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
13410
|
+
function ledgerTampering(cwd) {
|
|
13411
|
+
const file = ledgerPath(cwd);
|
|
13412
|
+
if (!file.ok)
|
|
13413
|
+
return [];
|
|
13604
13414
|
try {
|
|
13605
|
-
const
|
|
13606
|
-
|
|
13607
|
-
|
|
13608
|
-
|
|
13609
|
-
if (hasOpaqueLineBreak(text))
|
|
13610
|
-
return { path: file, added: 0, deleted: 0, binary: true };
|
|
13611
|
-
const lines = text.split(`
|
|
13612
|
-
`).length - (text.endsWith(`
|
|
13613
|
-
`) ? 1 : 0);
|
|
13614
|
-
return { path: file, added: lines, deleted: 0 };
|
|
13415
|
+
const stat = fs11.statSync(file.value);
|
|
13416
|
+
const cached = tamperCache.get(file.value);
|
|
13417
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
13418
|
+
return cached.breaks;
|
|
13615
13419
|
} catch {
|
|
13616
|
-
return
|
|
13420
|
+
return [];
|
|
13617
13421
|
}
|
|
13422
|
+
const read = readLedger(cwd, { integrity: true });
|
|
13423
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
13618
13424
|
}
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13623
|
-
|
|
13425
|
+
function tamperedUnblock(cwd) {
|
|
13426
|
+
const file = ledgerPath(cwd);
|
|
13427
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
13428
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
13429
|
+
}
|
|
13430
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
13431
|
+
function verdictBasis(verdict, current) {
|
|
13432
|
+
if (verdict.head && verdict.head === current.head)
|
|
13433
|
+
return "fresh";
|
|
13434
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
13435
|
+
return "carried";
|
|
13436
|
+
return "stale";
|
|
13437
|
+
}
|
|
13438
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
13439
|
+
function trailerSessions(cwd, base, head) {
|
|
13440
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
13441
|
+
return [];
|
|
13442
|
+
const out = git3(cwd, [
|
|
13443
|
+
"log",
|
|
13444
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
13445
|
+
`${base}..${head}`
|
|
13446
|
+
]);
|
|
13447
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
13448
|
+
}
|
|
13449
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
13450
|
+
function branchScope(cwd, branch) {
|
|
13451
|
+
const renamedAt = new Map;
|
|
13452
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
13453
|
+
if (safeRef2(branch)) {
|
|
13454
|
+
const log = git3(cwd, [
|
|
13455
|
+
"log",
|
|
13456
|
+
"-g",
|
|
13457
|
+
"--date=unix",
|
|
13458
|
+
"--format=%gd%x09%gs",
|
|
13459
|
+
`refs/heads/${branch}`,
|
|
13460
|
+
"--"
|
|
13461
|
+
]);
|
|
13462
|
+
for (const line of (log ?? "").split(`
|
|
13463
|
+
`)) {
|
|
13464
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
13465
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
13466
|
+
if (!Number.isFinite(seconds))
|
|
13467
|
+
continue;
|
|
13468
|
+
since = seconds * 1000;
|
|
13469
|
+
const match = RENAMED.exec(subject);
|
|
13470
|
+
if (!match || match[1] === branch)
|
|
13471
|
+
continue;
|
|
13472
|
+
const until = (seconds + 1) * 1000;
|
|
13473
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
13474
|
+
}
|
|
13475
|
+
}
|
|
13476
|
+
return (row) => {
|
|
13477
|
+
if (row.branch === branch)
|
|
13478
|
+
return true;
|
|
13479
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
13480
|
+
if (until === undefined)
|
|
13481
|
+
return false;
|
|
13482
|
+
const at = Date.parse(row.at);
|
|
13483
|
+
return at >= since && at <= until;
|
|
13484
|
+
};
|
|
13485
|
+
}
|
|
13486
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
13487
|
+
return (target) => {
|
|
13488
|
+
const own = typeof branch === "string" ? branch : null;
|
|
13489
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
13490
|
+
if (own === theirs)
|
|
13491
|
+
return true;
|
|
13492
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
13493
|
+
return false;
|
|
13494
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
13495
|
+
};
|
|
13496
|
+
};
|
|
13497
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
13498
|
+
const sessions = new Set;
|
|
13499
|
+
for (const row of rows) {
|
|
13500
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
13501
|
+
continue;
|
|
13502
|
+
const session = str(row.session) ?? row.actor.session;
|
|
13503
|
+
if (session)
|
|
13504
|
+
sessions.add(session);
|
|
13505
|
+
}
|
|
13506
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
13507
|
+
sessions.add(session);
|
|
13508
|
+
return sessions;
|
|
13509
|
+
}
|
|
13510
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
13511
|
+
var authoredBy = (authors, session) => {
|
|
13512
|
+
if (authors.has(session))
|
|
13513
|
+
return true;
|
|
13514
|
+
for (const author of authors)
|
|
13515
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
13516
|
+
return true;
|
|
13517
|
+
return false;
|
|
13518
|
+
};
|
|
13519
|
+
var TRUST_REASONS = new Set([
|
|
13520
|
+
"unsigned",
|
|
13521
|
+
"hash_mismatch",
|
|
13522
|
+
"broken_link",
|
|
13523
|
+
"duplicate_id",
|
|
13524
|
+
"unissued_session",
|
|
13525
|
+
"unbound_session"
|
|
13526
|
+
]);
|
|
13527
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
13528
|
+
"cli",
|
|
13529
|
+
"opencode",
|
|
13530
|
+
"pi",
|
|
13531
|
+
"codex_cli",
|
|
13532
|
+
"codex_desktop"
|
|
13533
|
+
]);
|
|
13534
|
+
function sessionTrust(cwd, row) {
|
|
13535
|
+
const session = row.actor.session;
|
|
13536
|
+
if (!session || row.self === true)
|
|
13624
13537
|
return null;
|
|
13625
|
-
const
|
|
13626
|
-
const
|
|
13627
|
-
|
|
13538
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
13539
|
+
const host = row.actor.host;
|
|
13540
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
13541
|
+
if (!issuedOnly)
|
|
13628
13542
|
return null;
|
|
13629
|
-
const
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13638
|
-
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13543
|
+
const binding = issuedBinding(cwd, session);
|
|
13544
|
+
if (binding === null)
|
|
13545
|
+
return "unissued_session";
|
|
13546
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
13547
|
+
}
|
|
13548
|
+
function trustReasons(cwd, row) {
|
|
13549
|
+
if (row.integrity === "unverified")
|
|
13550
|
+
return [row.integrityReason ?? "unsigned"];
|
|
13551
|
+
if (row.integrity !== "verified")
|
|
13552
|
+
return ["unsigned"];
|
|
13553
|
+
const session = sessionTrust(cwd, row);
|
|
13554
|
+
return session ? [session] : [];
|
|
13555
|
+
}
|
|
13556
|
+
function withIntegrity(cwd, rows) {
|
|
13557
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
13558
|
+
return rows;
|
|
13559
|
+
const read = readLedger(cwd, { integrity: true });
|
|
13560
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
13561
|
+
return rows.map((row) => {
|
|
13562
|
+
if (row.integrity !== undefined)
|
|
13563
|
+
return row;
|
|
13564
|
+
const fresh = bySeq.get(row.seq);
|
|
13565
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
13566
|
+
});
|
|
13567
|
+
}
|
|
13568
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
13569
|
+
var independenceReasons = (row, authors) => {
|
|
13570
|
+
const reasons = [];
|
|
13571
|
+
if (!row.actor.session)
|
|
13572
|
+
reasons.push("no_session");
|
|
13573
|
+
else if (authoredBy(authors, row.actor.session))
|
|
13574
|
+
reasons.push("author_session");
|
|
13575
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
13576
|
+
reasons.push("self");
|
|
13577
|
+
return reasons;
|
|
13578
|
+
};
|
|
13579
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
13580
|
+
let effective = null;
|
|
13581
|
+
for (const row of rows) {
|
|
13582
|
+
if (!effective) {
|
|
13583
|
+
effective = row;
|
|
13584
|
+
continue;
|
|
13585
|
+
}
|
|
13586
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
13587
|
+
continue;
|
|
13588
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
13589
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
13590
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
13591
|
+
continue;
|
|
13592
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
13593
|
+
continue;
|
|
13594
|
+
effective = row;
|
|
13644
13595
|
}
|
|
13645
|
-
return
|
|
13596
|
+
return effective;
|
|
13646
13597
|
}
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
const
|
|
13655
|
-
|
|
13656
|
-
|
|
13598
|
+
var restackExact = new WeakMap;
|
|
13599
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
13600
|
+
if (!from || !to)
|
|
13601
|
+
return false;
|
|
13602
|
+
const exact = (row, fromHead, head) => {
|
|
13603
|
+
if (exactIn === null)
|
|
13604
|
+
return true;
|
|
13605
|
+
const cwd = exactIn;
|
|
13606
|
+
let known = restackExact.get(row);
|
|
13607
|
+
if (known === undefined) {
|
|
13608
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
13609
|
+
from: fromHead,
|
|
13610
|
+
to: head,
|
|
13611
|
+
fromBase: row.fromBase,
|
|
13612
|
+
toBase: row.toBase
|
|
13613
|
+
});
|
|
13614
|
+
restackExact.set(row, known);
|
|
13615
|
+
}
|
|
13616
|
+
return known;
|
|
13617
|
+
};
|
|
13618
|
+
const edges = new Map;
|
|
13619
|
+
for (const row of rows)
|
|
13620
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head && exact(row, row.fromHead, row.head))
|
|
13621
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
13622
|
+
const seen = new Set([from]);
|
|
13623
|
+
const queue = [from];
|
|
13624
|
+
while (queue.length) {
|
|
13625
|
+
const next = queue.shift();
|
|
13626
|
+
if (next === to)
|
|
13627
|
+
return true;
|
|
13628
|
+
for (const target of edges.get(next) ?? [])
|
|
13629
|
+
if (!seen.has(target)) {
|
|
13630
|
+
seen.add(target);
|
|
13631
|
+
queue.push(target);
|
|
13632
|
+
}
|
|
13657
13633
|
}
|
|
13658
|
-
return
|
|
13659
|
-
}
|
|
13660
|
-
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
13661
|
-
function laneFiles(cwd, fork, head) {
|
|
13662
|
-
const stats = diffNumstat(cwd, fork, head);
|
|
13663
|
-
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
13664
|
-
if (!stats || !cosmetic)
|
|
13665
|
-
return null;
|
|
13666
|
-
const files = [
|
|
13667
|
-
...stats.map((entry) => ({
|
|
13668
|
-
...entry,
|
|
13669
|
-
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
13670
|
-
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
13671
|
-
})),
|
|
13672
|
-
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
13673
|
-
];
|
|
13674
|
-
const generated = generatedAt(cwd, fork, files.map((entry) => entry.path));
|
|
13675
|
-
return files.map((entry) => ({
|
|
13676
|
-
...entry,
|
|
13677
|
-
generated: generated.has(entry.path)
|
|
13678
|
-
}));
|
|
13634
|
+
return false;
|
|
13679
13635
|
}
|
|
13680
|
-
function
|
|
13681
|
-
const
|
|
13682
|
-
const
|
|
13683
|
-
const
|
|
13684
|
-
const
|
|
13685
|
-
const
|
|
13686
|
-
const
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13636
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
13637
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
13638
|
+
const head = branchHead(cwd, branch);
|
|
13639
|
+
const fallbackBase = defaultBase(cwd);
|
|
13640
|
+
const scope = branchScope(cwd, branch);
|
|
13641
|
+
const keyCache = new Map;
|
|
13642
|
+
const keysAt = (base, at) => {
|
|
13643
|
+
if (!base || !at)
|
|
13644
|
+
return { patchId: null, diffHash: null };
|
|
13645
|
+
const cacheKey = `${base}\x00${at}`;
|
|
13646
|
+
let key = keyCache.get(cacheKey);
|
|
13647
|
+
if (!key) {
|
|
13648
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
13649
|
+
keyCache.set(cacheKey, key);
|
|
13650
|
+
}
|
|
13651
|
+
return key;
|
|
13652
|
+
};
|
|
13653
|
+
const keyFor = (base) => keysAt(base, head);
|
|
13654
|
+
const driftCache = new Map;
|
|
13655
|
+
const driftOf = (row) => {
|
|
13656
|
+
if (driftCache.has(row))
|
|
13657
|
+
return driftCache.get(row) ?? null;
|
|
13658
|
+
let drift = null;
|
|
13659
|
+
const toBase = row.base ?? fallbackBase;
|
|
13660
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
13661
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
13662
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
13663
|
+
drift = verdictDrift(cwd, {
|
|
13664
|
+
from: row.head,
|
|
13665
|
+
to: head,
|
|
13666
|
+
fromBase,
|
|
13667
|
+
toBase: toBaseSha,
|
|
13668
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
13669
|
+
});
|
|
13670
|
+
driftCache.set(row, drift);
|
|
13671
|
+
return drift;
|
|
13672
|
+
};
|
|
13673
|
+
const exactCache = new Map;
|
|
13674
|
+
const exactCarry = (row) => {
|
|
13675
|
+
if (FAILING.has(String(row.result)))
|
|
13676
|
+
return true;
|
|
13677
|
+
let exact = exactCache.get(row);
|
|
13678
|
+
if (exact === undefined) {
|
|
13679
|
+
const toBase = row.base ?? fallbackBase;
|
|
13680
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
13681
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
13682
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
13683
|
+
exactCache.set(row, exact);
|
|
13684
|
+
}
|
|
13685
|
+
return exact;
|
|
13686
|
+
};
|
|
13687
|
+
const keyBasis = (row) => {
|
|
13688
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
13689
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
13690
|
+
};
|
|
13691
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
13692
|
+
const carryOf = (row) => {
|
|
13693
|
+
if (keyBasis(row) !== "stale")
|
|
13694
|
+
return null;
|
|
13695
|
+
if (restacked(row))
|
|
13696
|
+
return null;
|
|
13697
|
+
const drift = driftOf(row);
|
|
13698
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
13699
|
+
};
|
|
13700
|
+
const basisOf = (row) => {
|
|
13701
|
+
const basis = keyBasis(row);
|
|
13702
|
+
if (basis !== "stale")
|
|
13703
|
+
return basis;
|
|
13704
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
13705
|
+
};
|
|
13706
|
+
const sameCode = (failed, row) => {
|
|
13707
|
+
if (failed.head && failed.head === row.head)
|
|
13708
|
+
return true;
|
|
13709
|
+
const base = failed.base ?? fallbackBase;
|
|
13710
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
13711
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
13712
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
13713
|
+
};
|
|
13714
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
13715
|
+
const failureAuthorCache = new Map;
|
|
13716
|
+
const failureAuthors = (row) => {
|
|
13717
|
+
let set = failureAuthorCache.get(row.seq);
|
|
13718
|
+
if (!set) {
|
|
13719
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
13720
|
+
failureAuthorCache.set(row.seq, set);
|
|
13721
|
+
}
|
|
13722
|
+
return set;
|
|
13723
|
+
};
|
|
13724
|
+
const reasonCache = new Map;
|
|
13725
|
+
const reasonsOf = (row) => {
|
|
13726
|
+
let reasons = reasonCache.get(row);
|
|
13727
|
+
if (!reasons) {
|
|
13728
|
+
const failing = FAILING.has(String(row.result));
|
|
13729
|
+
reasons = [
|
|
13730
|
+
...trustReasons(cwd, row),
|
|
13731
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
13732
|
+
];
|
|
13733
|
+
reasonCache.set(row, reasons);
|
|
13734
|
+
}
|
|
13735
|
+
return reasons;
|
|
13736
|
+
};
|
|
13737
|
+
const retired = new Set;
|
|
13738
|
+
for (const row of rows)
|
|
13739
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
13740
|
+
retired.add(row.supersedes);
|
|
13741
|
+
const byKind = new Map;
|
|
13742
|
+
for (const row of rows)
|
|
13743
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
13744
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
13745
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
13746
|
+
}
|
|
13747
|
+
const verdicts = [];
|
|
13748
|
+
const warnings = [];
|
|
13749
|
+
for (const [kind, list] of byKind) {
|
|
13750
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
13751
|
+
if (!row)
|
|
13752
|
+
continue;
|
|
13753
|
+
const basis = basisOf(row);
|
|
13754
|
+
const current = basis !== "stale";
|
|
13755
|
+
const independence = reasonsOf(row);
|
|
13756
|
+
const reasons = [
|
|
13757
|
+
...current ? [] : ["stale"],
|
|
13758
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
13759
|
+
...independence
|
|
13760
|
+
];
|
|
13761
|
+
verdicts.push({
|
|
13762
|
+
kind,
|
|
13763
|
+
basis,
|
|
13764
|
+
current,
|
|
13765
|
+
independent: independence.length === 0,
|
|
13766
|
+
accepted: reasons.length === 0,
|
|
13767
|
+
reasons,
|
|
13768
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
13769
|
+
verdict: row
|
|
13770
|
+
});
|
|
13771
|
+
if (reasons.length === 0) {
|
|
13772
|
+
for (const later of list)
|
|
13773
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
13774
|
+
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
13775
|
+
}
|
|
13701
13776
|
}
|
|
13702
|
-
|
|
13777
|
+
for (const row of rows)
|
|
13778
|
+
if (row.integrity === "unverified" && scope(row))
|
|
13779
|
+
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
13780
|
+
const tampered = ledgerTampering(cwd);
|
|
13781
|
+
if (tampered.length) {
|
|
13782
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
13783
|
+
for (const entry of verdicts) {
|
|
13784
|
+
entry.accepted = false;
|
|
13785
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
13786
|
+
}
|
|
13787
|
+
}
|
|
13788
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
13789
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
13790
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
13791
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
13792
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
13793
|
+
accepted: false,
|
|
13794
|
+
verdict: failing?.verdict ?? null,
|
|
13795
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
13796
|
+
};
|
|
13797
|
+
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
13798
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
13799
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
13800
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
13801
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
13802
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
13703
13803
|
return {
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
base,
|
|
13710
|
-
|
|
13804
|
+
branch,
|
|
13805
|
+
head,
|
|
13806
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
13807
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
13808
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
13809
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
13810
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
13811
|
+
carry: carryEntry?.carry ?? null,
|
|
13812
|
+
reverify,
|
|
13813
|
+
accepted,
|
|
13814
|
+
verdicts,
|
|
13815
|
+
authors: [...authors].toSorted(),
|
|
13816
|
+
warnings
|
|
13711
13817
|
};
|
|
13712
13818
|
}
|
|
13713
|
-
var
|
|
13819
|
+
var fromLead = (row, leads) => leads === null || row.actor.session !== null && leads.includes(row.actor.session);
|
|
13820
|
+
function activeStanding(rows, fanout, leads = null) {
|
|
13821
|
+
const active = [];
|
|
13822
|
+
for (const row of rows) {
|
|
13823
|
+
if (row.fanout !== fanout || !fromLead(row, leads))
|
|
13824
|
+
continue;
|
|
13825
|
+
if (row.type === "standing" && typeof row.what === "string" && row.what.trim()) {
|
|
13826
|
+
if (!row.superseded)
|
|
13827
|
+
active.push(row);
|
|
13828
|
+
} else if (row.type === "standing.cleared") {
|
|
13829
|
+
const target = str(row.target);
|
|
13830
|
+
if (target === null)
|
|
13831
|
+
active.length = 0;
|
|
13832
|
+
else {
|
|
13833
|
+
const index = active.findIndex((order) => order.id === target);
|
|
13834
|
+
if (index >= 0)
|
|
13835
|
+
active.splice(index, 1);
|
|
13836
|
+
}
|
|
13837
|
+
}
|
|
13838
|
+
}
|
|
13839
|
+
return active;
|
|
13840
|
+
}
|
|
13841
|
+
var OBSERVED_ONLY = new Set([
|
|
13842
|
+
"check",
|
|
13843
|
+
"merge.unverified",
|
|
13844
|
+
"policy.judged",
|
|
13845
|
+
...PR_ROW_TYPES
|
|
13846
|
+
]);
|
|
13714
13847
|
|
|
13715
13848
|
// packages/workit-core/src/core/policy/derive.ts
|
|
13716
13849
|
var RULES = {
|
|
@@ -17724,6 +17857,18 @@ var substitutions = (body) => {
|
|
|
17724
17857
|
};
|
|
17725
17858
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
17726
17859
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
17860
|
+
var expandsInQuotes = (body, dialect) => {
|
|
17861
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
17862
|
+
for (let index = 0;index < body.length; index++) {
|
|
17863
|
+
const char = body[index];
|
|
17864
|
+
if (char === escape)
|
|
17865
|
+
index++;
|
|
17866
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
17867
|
+
return true;
|
|
17868
|
+
}
|
|
17869
|
+
return false;
|
|
17870
|
+
};
|
|
17871
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
17727
17872
|
var arithmeticEnd = (command, start) => {
|
|
17728
17873
|
const end = substitutionEnd(command, start);
|
|
17729
17874
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -17742,6 +17887,7 @@ var tokenize = (command, dialect) => {
|
|
|
17742
17887
|
let current = { words: [], redirects: [] };
|
|
17743
17888
|
let word = "";
|
|
17744
17889
|
let inWord = false;
|
|
17890
|
+
let expanding = false;
|
|
17745
17891
|
let pending = null;
|
|
17746
17892
|
const heredocs = [];
|
|
17747
17893
|
const escape = posix ? "\\" : "`";
|
|
@@ -17756,11 +17902,16 @@ var tokenize = (command, dialect) => {
|
|
|
17756
17902
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
17757
17903
|
else if (pending === "herestring")
|
|
17758
17904
|
(current.stdin ??= []).push(text);
|
|
17759
|
-
else if (pending !== "input")
|
|
17905
|
+
else if (pending !== "input") {
|
|
17906
|
+
const at = current.words.length;
|
|
17760
17907
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
17908
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
17909
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
17910
|
+
}
|
|
17761
17911
|
pending = null;
|
|
17762
17912
|
word = "";
|
|
17763
17913
|
inWord = false;
|
|
17914
|
+
expanding = false;
|
|
17764
17915
|
};
|
|
17765
17916
|
const flushSegment = () => {
|
|
17766
17917
|
flushWord();
|
|
@@ -17775,6 +17926,7 @@ var tokenize = (command, dialect) => {
|
|
|
17775
17926
|
};
|
|
17776
17927
|
const substitution = (inner, text) => {
|
|
17777
17928
|
(current.subs ??= []).push(inner);
|
|
17929
|
+
expanding = true;
|
|
17778
17930
|
word += text;
|
|
17779
17931
|
inWord = true;
|
|
17780
17932
|
};
|
|
@@ -17785,6 +17937,8 @@ var tokenize = (command, dialect) => {
|
|
|
17785
17937
|
if (run) {
|
|
17786
17938
|
word += run[0];
|
|
17787
17939
|
inWord = true;
|
|
17940
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
17941
|
+
expanding = true;
|
|
17788
17942
|
index += run[0].length - 1;
|
|
17789
17943
|
continue;
|
|
17790
17944
|
}
|
|
@@ -17813,6 +17967,8 @@ var tokenize = (command, dialect) => {
|
|
|
17813
17967
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
17814
17968
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
17815
17969
|
(current.subs ??= []).push(...substitutions(body));
|
|
17970
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
17971
|
+
expanding = true;
|
|
17816
17972
|
word += unquote(body, char, dialect);
|
|
17817
17973
|
inWord = true;
|
|
17818
17974
|
index = end < 0 ? command.length : end;
|
|
@@ -17858,6 +18014,8 @@ var tokenize = (command, dialect) => {
|
|
|
17858
18014
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
17859
18015
|
word = word.slice(0, -1);
|
|
17860
18016
|
inWord = word !== "";
|
|
18017
|
+
if (!inWord)
|
|
18018
|
+
expanding = false;
|
|
17861
18019
|
}
|
|
17862
18020
|
group(char === "(" ? "open" : "close");
|
|
17863
18021
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -17911,6 +18069,8 @@ var tokenize = (command, dialect) => {
|
|
|
17911
18069
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
17912
18070
|
word += CLOSE;
|
|
17913
18071
|
else {
|
|
18072
|
+
if (char === "$")
|
|
18073
|
+
expanding = true;
|
|
17914
18074
|
word += char;
|
|
17915
18075
|
inWord = true;
|
|
17916
18076
|
}
|
|
@@ -17965,7 +18125,7 @@ var GIT_VALUE_OPTIONS = new Set([
|
|
|
17965
18125
|
]);
|
|
17966
18126
|
var GIT_CONFIG_OPTIONS = new Set(["-c", "--config-env", "--git-dir"]);
|
|
17967
18127
|
var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
17968
|
-
var
|
|
18128
|
+
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
17969
18129
|
var MAX_DEPTH = 4;
|
|
17970
18130
|
var MAX_EXPANSIONS = 16;
|
|
17971
18131
|
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
@@ -17992,7 +18152,7 @@ var stripPrefixes = (input) => {
|
|
|
17992
18152
|
const first = words[0];
|
|
17993
18153
|
if (first === undefined)
|
|
17994
18154
|
return { words, env };
|
|
17995
|
-
const assignment =
|
|
18155
|
+
const assignment = ASSIGNMENT.exec(first);
|
|
17996
18156
|
if (assignment) {
|
|
17997
18157
|
if (GIT_CONFIG_ENV.test(assignment[1]))
|
|
17998
18158
|
env[assignment[1]] = assignment[2];
|
|
@@ -18051,10 +18211,10 @@ var assignments = (words, vars) => {
|
|
|
18051
18211
|
while (rest[0]?.startsWith("-"))
|
|
18052
18212
|
rest = rest.slice(1);
|
|
18053
18213
|
}
|
|
18054
|
-
if (rest.length === 0 || !rest.every((word) =>
|
|
18214
|
+
if (rest.length === 0 || !rest.every((word) => ASSIGNMENT.test(word)))
|
|
18055
18215
|
return null;
|
|
18056
18216
|
return rest.map((word) => {
|
|
18057
|
-
const [, name, value] =
|
|
18217
|
+
const [, name, value] = ASSIGNMENT.exec(word);
|
|
18058
18218
|
return [name, expandWord(value, vars)];
|
|
18059
18219
|
});
|
|
18060
18220
|
};
|
|
@@ -18242,7 +18402,7 @@ var splitString = (line) => {
|
|
|
18242
18402
|
return line.slice(index).join(" ").slice(15);
|
|
18243
18403
|
if (/^-S./.test(word))
|
|
18244
18404
|
return [word.slice(2), ...line.slice(index + 1)].join(" ");
|
|
18245
|
-
if (!word.startsWith("-") && !
|
|
18405
|
+
if (!word.startsWith("-") && !ASSIGNMENT.test(word))
|
|
18246
18406
|
return null;
|
|
18247
18407
|
}
|
|
18248
18408
|
return null;
|
|
@@ -18355,8 +18515,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
18355
18515
|
visit(next, undefined, hops + 1);
|
|
18356
18516
|
};
|
|
18357
18517
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
18358
|
-
if (wrapped)
|
|
18359
|
-
|
|
18518
|
+
if (wrapped) {
|
|
18519
|
+
const outer = dir;
|
|
18520
|
+
if (wrapped.cwd !== null)
|
|
18521
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
18522
|
+
if ("script" in wrapped)
|
|
18523
|
+
nested(wrapped.script, "posix");
|
|
18524
|
+
else
|
|
18525
|
+
runs([wrapped.argv]);
|
|
18526
|
+
dir = outer;
|
|
18527
|
+
return;
|
|
18528
|
+
}
|
|
18360
18529
|
if (CD.has(name)) {
|
|
18361
18530
|
const target = cdTarget(words);
|
|
18362
18531
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -18457,14 +18626,14 @@ function workitCheckTail(words) {
|
|
|
18457
18626
|
return null;
|
|
18458
18627
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
18459
18628
|
return null;
|
|
18460
|
-
const args = words.slice(at + 1);
|
|
18629
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
18461
18630
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
18462
18631
|
return null;
|
|
18463
18632
|
const split = args.indexOf("--");
|
|
18464
18633
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
18465
18634
|
if (!tail.length)
|
|
18466
18635
|
return null;
|
|
18467
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18636
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
18468
18637
|
}
|
|
18469
18638
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
18470
18639
|
let segments;
|
|
@@ -18525,7 +18694,21 @@ function skipWorkitGlobals(args) {
|
|
|
18525
18694
|
} else if (args[at] !== "--json")
|
|
18526
18695
|
break;
|
|
18527
18696
|
}
|
|
18528
|
-
|
|
18697
|
+
const rest = [];
|
|
18698
|
+
for (;at < args.length; at++) {
|
|
18699
|
+
const arg = args[at];
|
|
18700
|
+
if (arg === "--") {
|
|
18701
|
+
rest.push(...args.slice(at));
|
|
18702
|
+
break;
|
|
18703
|
+
}
|
|
18704
|
+
if (arg.startsWith("--cwd="))
|
|
18705
|
+
cwd = arg.slice("--cwd=".length);
|
|
18706
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
18707
|
+
cwd = args[++at];
|
|
18708
|
+
else
|
|
18709
|
+
rest.push(arg);
|
|
18710
|
+
}
|
|
18711
|
+
return { rest, cwd };
|
|
18529
18712
|
}
|
|
18530
18713
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
18531
18714
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
@@ -18971,7 +19154,7 @@ var rewrittenUrl = (url, config) => {
|
|
|
18971
19154
|
}
|
|
18972
19155
|
return url;
|
|
18973
19156
|
};
|
|
18974
|
-
var
|
|
19157
|
+
var pushUrl = (config, remote, dir) => {
|
|
18975
19158
|
const configured = configValue(config, `remote.${remote}.pushurl`) ?? configValue(config, `remote.${remote}.url`);
|
|
18976
19159
|
return normalizedUrl(rewrittenUrl(configured ?? remote, config), dir);
|
|
18977
19160
|
};
|
|
@@ -18999,7 +19182,7 @@ var toPushRemote = (repo, remote, config) => {
|
|
|
18999
19182
|
const pushRemote = pushRemoteOf(config, repo.branch);
|
|
19000
19183
|
if (pushRemote === null || pushRemote === remote)
|
|
19001
19184
|
return true;
|
|
19002
|
-
return
|
|
19185
|
+
return pushUrl(config, remote, repo.dir) === pushUrl(config, pushRemote, repo.dir);
|
|
19003
19186
|
};
|
|
19004
19187
|
var quote = (text) => `\`${text}\``;
|
|
19005
19188
|
var refusal = (action, branch, dir) => {
|
|
@@ -19537,6 +19720,53 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
19537
19720
|
}
|
|
19538
19721
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
19539
19722
|
};
|
|
19723
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
19724
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
19725
|
+
"git",
|
|
19726
|
+
"pr",
|
|
19727
|
+
"ci",
|
|
19728
|
+
"stack",
|
|
19729
|
+
"release",
|
|
19730
|
+
"task",
|
|
19731
|
+
"ledger",
|
|
19732
|
+
"evidence",
|
|
19733
|
+
"finding",
|
|
19734
|
+
"decision",
|
|
19735
|
+
"worker",
|
|
19736
|
+
"verify-delivery",
|
|
19737
|
+
"policy",
|
|
19738
|
+
"doctor",
|
|
19739
|
+
"help"
|
|
19740
|
+
];
|
|
19741
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
19742
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
19743
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
19744
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
19745
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
19746
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
19747
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
19748
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
19749
|
+
if (segment.unanalyzable) {
|
|
19750
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
19751
|
+
if (!/\bworkit\b/u.test(text))
|
|
19752
|
+
return null;
|
|
19753
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
19754
|
+
return found ? bypassReason(found) : null;
|
|
19755
|
+
}
|
|
19756
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
19757
|
+
if (start < 0)
|
|
19758
|
+
continue;
|
|
19759
|
+
const words = segment.words.slice(start + 1);
|
|
19760
|
+
const end = words.indexOf("--");
|
|
19761
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
19762
|
+
if (flag)
|
|
19763
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
19764
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
19765
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
19766
|
+
return EXPANSION_REASON;
|
|
19767
|
+
}
|
|
19768
|
+
return null;
|
|
19769
|
+
};
|
|
19540
19770
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
19541
19771
|
import fs16 from "node:fs";
|
|
19542
19772
|
import os6 from "node:os";
|
|
@@ -20860,7 +21090,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20860
21090
|
}
|
|
20861
21091
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20862
21092
|
if (inner !== null && dir !== undefined)
|
|
20863
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21093
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
20864
21094
|
if (strict && dir !== undefined) {
|
|
20865
21095
|
const runIn = dir;
|
|
20866
21096
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -20900,6 +21130,14 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20900
21130
|
}
|
|
20901
21131
|
return targets;
|
|
20902
21132
|
};
|
|
21133
|
+
var workitRunDir = (dir, words) => {
|
|
21134
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
21135
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
21136
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
21137
|
+
return at;
|
|
21138
|
+
}
|
|
21139
|
+
return dir;
|
|
21140
|
+
};
|
|
20903
21141
|
var workitCwd = (dir, args) => {
|
|
20904
21142
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
20905
21143
|
return { at: cwd === null ? dir : path25.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
@@ -21684,8 +21922,13 @@ var standingDelta = (input) => {
|
|
|
21684
21922
|
};
|
|
21685
21923
|
var sinceStart = new Map;
|
|
21686
21924
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
21687
|
-
var
|
|
21925
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
21926
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
21927
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
21928
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
21929
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
21688
21930
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
21931
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
21689
21932
|
var stdinOnly = (words) => {
|
|
21690
21933
|
const [name, ...args] = words;
|
|
21691
21934
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -21707,14 +21950,28 @@ var cwdRewrite = (command) => {
|
|
|
21707
21950
|
return null;
|
|
21708
21951
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
21709
21952
|
return null;
|
|
21710
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
21711
21953
|
if (!segments.every(clean))
|
|
21712
21954
|
return null;
|
|
21713
21955
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
21714
21956
|
return null;
|
|
21715
21957
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
21716
21958
|
return null;
|
|
21717
|
-
|
|
21959
|
+
const args = rest.slice("workit".length).trimStart();
|
|
21960
|
+
const verb = VERB_FIRST.exec(args);
|
|
21961
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
21962
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
21963
|
+
const json = verb[1] ? " --json" : "";
|
|
21964
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
21965
|
+
};
|
|
21966
|
+
var verbFirst = (command) => {
|
|
21967
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
21968
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
21969
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
21970
|
+
return null;
|
|
21971
|
+
const segments = segmentsOf(command);
|
|
21972
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
21973
|
+
return null;
|
|
21974
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
21718
21975
|
};
|
|
21719
21976
|
|
|
21720
21977
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
@@ -21830,7 +22087,7 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
21830
22087
|
};
|
|
21831
22088
|
var shown = (target) => path28.basename(target) === ".workit-live-target" ? path28.dirname(target) : target;
|
|
21832
22089
|
var unboundDeny = (input, target) => {
|
|
21833
|
-
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree>
|
|
22090
|
+
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit git --cwd <worktree> commit -m … -- <paths>`, `workit check --cwd <worktree> <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
21834
22091
|
return {
|
|
21835
22092
|
kind: "deny",
|
|
21836
22093
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
|
|
@@ -21838,7 +22095,7 @@ var unboundDeny = (input, target) => {
|
|
|
21838
22095
|
};
|
|
21839
22096
|
};
|
|
21840
22097
|
var outsideDeny = (input, worktree, target) => {
|
|
21841
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
22098
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit git --cwd ${worktree} commit -m … -- <paths>\`, \`workit check --cwd ${worktree} <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
|
|
21842
22099
|
return {
|
|
21843
22100
|
kind: "deny",
|
|
21844
22101
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -21847,7 +22104,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
21847
22104
|
};
|
|
21848
22105
|
var unknownDirDeny = (input, worktree) => {
|
|
21849
22106
|
const wt = worktree ?? "<worktree>";
|
|
21850
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22107
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
21851
22108
|
return {
|
|
21852
22109
|
kind: "deny",
|
|
21853
22110
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -22005,7 +22262,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
22005
22262
|
return `${isolatedGit(input.cwd)}
|
|
22006
22263
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
22007
22264
|
if (role === "implementer")
|
|
22008
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree>
|
|
22265
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> 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.`;
|
|
22009
22266
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
22010
22267
|
if (role !== "judge" || !event.agentId)
|
|
22011
22268
|
return readOnly;
|
|
@@ -22022,7 +22279,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
22022
22279
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
22023
22280
|
function handleHook(input, deps) {
|
|
22024
22281
|
const decision = decide(input, deps);
|
|
22025
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
22282
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
22026
22283
|
return decision;
|
|
22027
22284
|
const delta = standingDelta(input);
|
|
22028
22285
|
if (!delta)
|
|
@@ -22089,9 +22346,12 @@ function decide(input, deps) {
|
|
|
22089
22346
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
22090
22347
|
return heavyDenial(event.command, plan);
|
|
22091
22348
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
22092
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
22349
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
22093
22350
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
22094
22351
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22352
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
22353
|
+
if (bypass)
|
|
22354
|
+
return { kind: "ask", reason: bypass };
|
|
22095
22355
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
22096
22356
|
const rewritten = (text) => rewrittenTo ? {
|
|
22097
22357
|
kind: "rewrite",
|
|
@@ -22518,6 +22778,17 @@ var render = (decision, native) => {
|
|
|
22518
22778
|
},
|
|
22519
22779
|
exitCode: 0
|
|
22520
22780
|
};
|
|
22781
|
+
if (decision.kind === "ask" && native === "PreToolUse")
|
|
22782
|
+
return {
|
|
22783
|
+
json: {
|
|
22784
|
+
hookSpecificOutput: {
|
|
22785
|
+
hookEventName: "PreToolUse",
|
|
22786
|
+
permissionDecision: "ask",
|
|
22787
|
+
permissionDecisionReason: decision.reason
|
|
22788
|
+
}
|
|
22789
|
+
},
|
|
22790
|
+
exitCode: 0
|
|
22791
|
+
};
|
|
22521
22792
|
if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
|
|
22522
22793
|
return {
|
|
22523
22794
|
json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
|