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