@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
package/dist/launch-mcp.js
CHANGED
|
@@ -14864,6 +14864,7 @@ var workspaceConfigSchema = object({
|
|
|
14864
14864
|
trailers: boolean2().optional(),
|
|
14865
14865
|
liveSessionGuard: boolean2().optional(),
|
|
14866
14866
|
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
14867
|
+
models: object({ roles: unknown().optional() }).passthrough().optional(),
|
|
14867
14868
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
14868
14869
|
kind,
|
|
14869
14870
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -14890,7 +14891,10 @@ var workspaceConfigSchema = object({
|
|
|
14890
14891
|
});
|
|
14891
14892
|
}
|
|
14892
14893
|
});
|
|
14893
|
-
var workspacesFileSchema = object({
|
|
14894
|
+
var workspacesFileSchema = object({
|
|
14895
|
+
workspaces: array(workspaceConfigSchema).optional(),
|
|
14896
|
+
models: object({ roles: unknown().optional() }).passthrough().optional()
|
|
14897
|
+
}).passthrough().superRefine((file, context) => {
|
|
14894
14898
|
const names = new Set;
|
|
14895
14899
|
for (const [index, workspace] of (file.workspaces ?? []).entries()) {
|
|
14896
14900
|
if (names.has(workspace.name)) {
|
|
@@ -15180,6 +15184,34 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
15180
15184
|
const matched = matchRuntimeWorkspace(cwd);
|
|
15181
15185
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
15182
15186
|
};
|
|
15187
|
+
var modelSources = (cwd, dir) => {
|
|
15188
|
+
const out = [];
|
|
15189
|
+
try {
|
|
15190
|
+
const matched = matchRuntimeWorkspace(cwd, dir);
|
|
15191
|
+
if (matched) {
|
|
15192
|
+
const enclosing = matched.matches.filter((entry) => entry !== matched.selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
15193
|
+
for (const entry of [matched.selected, ...enclosing])
|
|
15194
|
+
out.push({
|
|
15195
|
+
name: entry.name,
|
|
15196
|
+
global: false,
|
|
15197
|
+
models: entry.models,
|
|
15198
|
+
tierModels: entry.fanout?.tierModels
|
|
15199
|
+
});
|
|
15200
|
+
}
|
|
15201
|
+
} catch {}
|
|
15202
|
+
try {
|
|
15203
|
+
const document = JSON.parse(readFileSync2(path4.join(dir ?? configDir(), "workspaces.json"), "utf8"));
|
|
15204
|
+
if (document && typeof document === "object" && !Array.isArray(document))
|
|
15205
|
+
out.push({
|
|
15206
|
+
name: "global",
|
|
15207
|
+
global: true,
|
|
15208
|
+
models: document.models,
|
|
15209
|
+
tierModels: undefined
|
|
15210
|
+
});
|
|
15211
|
+
} catch {}
|
|
15212
|
+
return out;
|
|
15213
|
+
};
|
|
15214
|
+
var hostFamily = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
15183
15215
|
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
15184
15216
|
var TIER_HOSTS = new Set([
|
|
15185
15217
|
"claude_code",
|
|
@@ -15400,6 +15432,26 @@ function resolveRef(cwd, ref) {
|
|
|
15400
15432
|
return null;
|
|
15401
15433
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
15402
15434
|
}
|
|
15435
|
+
function originHeadRef(cwd) {
|
|
15436
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
15437
|
+
}
|
|
15438
|
+
function ownChangeDrift(cwd, range) {
|
|
15439
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
15440
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
15441
|
+
if (!before || !after)
|
|
15442
|
+
return null;
|
|
15443
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
15444
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
15445
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
15446
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
15447
|
+
}
|
|
15448
|
+
function sameOwnChange(cwd, range) {
|
|
15449
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
15450
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
15451
|
+
if (!fromFork || !toFork)
|
|
15452
|
+
return false;
|
|
15453
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
15454
|
+
}
|
|
15403
15455
|
var LANE_DIFF = [
|
|
15404
15456
|
"-c",
|
|
15405
15457
|
"core.quotePath=false",
|
|
@@ -19026,9 +19078,9 @@ var workerBlocksTransition = (transition, state) => transition === "pause" ? fal
|
|
|
19026
19078
|
|
|
19027
19079
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
19028
19080
|
import { createHash as createHash10 } from "node:crypto";
|
|
19029
|
-
import * as
|
|
19030
|
-
import { spawnSync as
|
|
19031
|
-
import
|
|
19081
|
+
import * as fs13 from "node:fs";
|
|
19082
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
19083
|
+
import path17 from "node:path";
|
|
19032
19084
|
|
|
19033
19085
|
// packages/workit-core/src/check-config.ts
|
|
19034
19086
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
@@ -19249,68 +19301,20 @@ function checkCommand(args, version, onPath = workitAvailable()) {
|
|
|
19249
19301
|
|
|
19250
19302
|
// packages/workit-core/src/core/policy/derive.ts
|
|
19251
19303
|
import { existsSync as existsSync5 } from "node:fs";
|
|
19252
|
-
import
|
|
19304
|
+
import path16 from "node:path";
|
|
19253
19305
|
|
|
19254
19306
|
// packages/workit-core/src/ledger.ts
|
|
19255
19307
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
19256
19308
|
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
19257
19309
|
import fs12 from "node:fs";
|
|
19310
|
+
import path15 from "node:path";
|
|
19311
|
+
|
|
19312
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
19313
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
19258
19314
|
import path14 from "node:path";
|
|
19259
|
-
|
|
19260
|
-
|
|
19261
|
-
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
19262
|
-
var FAILING = new Set(["failed", "blocked"]);
|
|
19263
|
-
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
19264
|
-
var PR_ROW_TYPES = new Set([
|
|
19265
|
-
"pr.created",
|
|
19266
|
-
"pr.status",
|
|
19267
|
-
"pr.merged",
|
|
19268
|
-
"pr.linked"
|
|
19269
|
-
]);
|
|
19270
|
-
var err = (code, error, unblock) => ({
|
|
19271
|
-
ok: false,
|
|
19272
|
-
code,
|
|
19273
|
-
error,
|
|
19274
|
-
...unblock ? { unblock } : {}
|
|
19275
|
-
});
|
|
19276
|
-
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
19277
|
-
const run = spawnSync9("git", args, {
|
|
19278
|
-
cwd,
|
|
19279
|
-
encoding: "utf8",
|
|
19280
|
-
timeout: timeoutMs,
|
|
19281
|
-
killSignal: "SIGKILL",
|
|
19282
|
-
windowsHide: true,
|
|
19283
|
-
maxBuffer: 256 * 1024 * 1024,
|
|
19284
|
-
env: {
|
|
19285
|
-
...process.env,
|
|
19286
|
-
GIT_TERMINAL_PROMPT: "0",
|
|
19287
|
-
GIT_OPTIONAL_LOCKS: "0",
|
|
19288
|
-
LC_ALL: "C",
|
|
19289
|
-
LANGUAGE: "C"
|
|
19290
|
-
}
|
|
19291
|
-
});
|
|
19292
|
-
return {
|
|
19293
|
-
ok: run.status === 0,
|
|
19294
|
-
stdout: run.stdout ?? "",
|
|
19295
|
-
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
19296
|
-
};
|
|
19297
|
-
};
|
|
19298
|
-
var git4 = (cwd, args) => {
|
|
19299
|
-
const run = gitRun2(cwd, args);
|
|
19300
|
-
const value = run.ok ? run.stdout.trim() : "";
|
|
19301
|
-
return value ? value : null;
|
|
19302
|
-
};
|
|
19315
|
+
|
|
19316
|
+
// packages/workit-core/src/git/default-base.ts
|
|
19303
19317
|
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
19304
|
-
function resolveCommit(cwd, ref) {
|
|
19305
|
-
if (!safeRef(ref))
|
|
19306
|
-
return null;
|
|
19307
|
-
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
19308
|
-
}
|
|
19309
|
-
function branchHead(cwd, branch) {
|
|
19310
|
-
if (!safeRef(branch))
|
|
19311
|
-
return null;
|
|
19312
|
-
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
19313
|
-
}
|
|
19314
19318
|
var trackTrunk = (cwd) => {
|
|
19315
19319
|
try {
|
|
19316
19320
|
const resolved = vcsConfig("resolve", cwd);
|
|
@@ -19326,11 +19330,11 @@ function defaultBase(cwd) {
|
|
|
19326
19330
|
const trunk = trackTrunk(cwd);
|
|
19327
19331
|
if (trunk) {
|
|
19328
19332
|
for (const candidate of [`origin/${trunk}`, trunk])
|
|
19329
|
-
if (
|
|
19333
|
+
if (resolveRef(cwd, candidate))
|
|
19330
19334
|
return candidate;
|
|
19331
19335
|
}
|
|
19332
|
-
const originHead =
|
|
19333
|
-
if (originHead &&
|
|
19336
|
+
const originHead = originHeadRef(cwd);
|
|
19337
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
19334
19338
|
return originHead;
|
|
19335
19339
|
for (const candidate of [
|
|
19336
19340
|
"origin/main",
|
|
@@ -19340,1489 +19344,1611 @@ function defaultBase(cwd) {
|
|
|
19340
19344
|
"origin/develop",
|
|
19341
19345
|
"develop"
|
|
19342
19346
|
])
|
|
19343
|
-
if (
|
|
19347
|
+
if (resolveRef(cwd, candidate))
|
|
19344
19348
|
return candidate;
|
|
19345
19349
|
return null;
|
|
19346
19350
|
}
|
|
19347
|
-
|
|
19348
|
-
|
|
19351
|
+
|
|
19352
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
19353
|
+
function patternRegex(dir, pattern) {
|
|
19354
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
19349
19355
|
return null;
|
|
19350
|
-
const
|
|
19351
|
-
|
|
19352
|
-
|
|
19353
|
-
|
|
19354
|
-
|
|
19355
|
-
"
|
|
19356
|
-
|
|
19357
|
-
|
|
19358
|
-
|
|
19359
|
-
"
|
|
19360
|
-
|
|
19361
|
-
"
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19356
|
+
const anchored = pattern.includes("/");
|
|
19357
|
+
const body = pattern.replace(/^\//u, "");
|
|
19358
|
+
let source = "";
|
|
19359
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
19360
|
+
const char = body[index];
|
|
19361
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
19362
|
+
const slash = body[index + 2] === "/";
|
|
19363
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
19364
|
+
index += slash ? 2 : 1;
|
|
19365
|
+
} else if (char === "*")
|
|
19366
|
+
source += "[^/]*";
|
|
19367
|
+
else if (char === "?")
|
|
19368
|
+
source += "[^/]";
|
|
19369
|
+
else if (char === "[") {
|
|
19370
|
+
const end = body.indexOf("]", index + 2);
|
|
19371
|
+
if (end < 0)
|
|
19372
|
+
return null;
|
|
19373
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
19374
|
+
source += `[${set}]`;
|
|
19375
|
+
index = end;
|
|
19376
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
19377
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
19378
|
+
index += 1;
|
|
19379
|
+
} else
|
|
19380
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
19381
|
+
}
|
|
19382
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
19383
|
+
try {
|
|
19384
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
19385
|
+
} catch {
|
|
19367
19386
|
return null;
|
|
19368
|
-
|
|
19369
|
-
|
|
19387
|
+
}
|
|
19388
|
+
}
|
|
19389
|
+
function rulesOf(dir, text) {
|
|
19390
|
+
const out = [];
|
|
19391
|
+
for (const raw of text.split(`
|
|
19370
19392
|
`)) {
|
|
19371
|
-
|
|
19393
|
+
const line = raw.trim();
|
|
19394
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
19372
19395
|
continue;
|
|
19373
|
-
|
|
19374
|
-
|
|
19375
|
-
|
|
19396
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
19397
|
+
let generated = null;
|
|
19398
|
+
for (const attribute of attributes) {
|
|
19399
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
19400
|
+
generated = true;
|
|
19401
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
19402
|
+
generated = false;
|
|
19403
|
+
}
|
|
19404
|
+
if (generated === null)
|
|
19405
|
+
continue;
|
|
19406
|
+
const regex = patternRegex(dir, pattern);
|
|
19407
|
+
if (regex)
|
|
19408
|
+
out.push({ regex, generated });
|
|
19376
19409
|
}
|
|
19377
|
-
return
|
|
19378
|
-
}
|
|
19379
|
-
function storeRoot(cwd) {
|
|
19380
|
-
const location = resolveStore(path14.resolve(cwd));
|
|
19381
|
-
if (location instanceof Error)
|
|
19382
|
-
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");
|
|
19383
|
-
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
19410
|
+
return out;
|
|
19384
19411
|
}
|
|
19385
|
-
function
|
|
19386
|
-
const
|
|
19387
|
-
|
|
19412
|
+
function generatedByAttributes(files, paths) {
|
|
19413
|
+
const rules = [...files].map(([file, text]) => {
|
|
19414
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
19415
|
+
return {
|
|
19416
|
+
depth: dir ? dir.split("/").length : 0,
|
|
19417
|
+
rules: rulesOf(dir, text)
|
|
19418
|
+
};
|
|
19419
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
19420
|
+
const out = new Set;
|
|
19421
|
+
for (const file of paths) {
|
|
19422
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
19423
|
+
if (last?.generated)
|
|
19424
|
+
out.add(file);
|
|
19425
|
+
}
|
|
19426
|
+
return out;
|
|
19388
19427
|
}
|
|
19389
|
-
|
|
19390
|
-
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
|
|
19394
|
-
|
|
19395
|
-
|
|
19396
|
-
|
|
19397
|
-
|
|
19398
|
-
|
|
19399
|
-
|
|
19400
|
-
|
|
19428
|
+
|
|
19429
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
19430
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
19431
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
19432
|
+
var SYNTAXES = Object.fromEntries([
|
|
19433
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
19434
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
19435
|
+
ext,
|
|
19436
|
+
{ ...C_LIKE, triple: true }
|
|
19437
|
+
]),
|
|
19438
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
19439
|
+
...["css", "scss", "less"].map((ext) => [
|
|
19440
|
+
ext,
|
|
19441
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
19442
|
+
]),
|
|
19443
|
+
...["py", "pyi"].map((ext) => [
|
|
19444
|
+
ext,
|
|
19445
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
19446
|
+
]),
|
|
19447
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
19448
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
19449
|
+
ext,
|
|
19450
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
19451
|
+
])
|
|
19401
19452
|
]);
|
|
19402
|
-
var
|
|
19403
|
-
var
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
19408
|
-
} catch {
|
|
19409
|
-
return false;
|
|
19410
|
-
}
|
|
19411
|
-
};
|
|
19412
|
-
var LOCK_WAIT_MS = 5000;
|
|
19413
|
-
var LOCK_STALE_MS = 30000;
|
|
19414
|
-
var LOCK_OWNER = "owner";
|
|
19415
|
-
var lockToken = (lock) => {
|
|
19416
|
-
try {
|
|
19417
|
-
return fs12.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
|
|
19418
|
-
} catch {
|
|
19419
|
-
return null;
|
|
19420
|
-
}
|
|
19421
|
-
};
|
|
19422
|
-
var lockAgeMs = (lock) => {
|
|
19423
|
-
for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
|
|
19424
|
-
try {
|
|
19425
|
-
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
19426
|
-
} catch {}
|
|
19427
|
-
}
|
|
19428
|
-
return null;
|
|
19453
|
+
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;
|
|
19454
|
+
var extensionOf = (file) => {
|
|
19455
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
19456
|
+
const dot = name.lastIndexOf(".");
|
|
19457
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
19429
19458
|
};
|
|
19430
|
-
var
|
|
19431
|
-
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
19440
|
-
|
|
19441
|
-
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19459
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
19460
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
19461
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
19462
|
+
var WORD = /[\w$]/u;
|
|
19463
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
19464
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
19465
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
19466
|
+
var REGEX_KEYWORDS = new Set([
|
|
19467
|
+
"return",
|
|
19468
|
+
"typeof",
|
|
19469
|
+
"instanceof",
|
|
19470
|
+
"case",
|
|
19471
|
+
"do",
|
|
19472
|
+
"else",
|
|
19473
|
+
"in",
|
|
19474
|
+
"of",
|
|
19475
|
+
"new",
|
|
19476
|
+
"delete",
|
|
19477
|
+
"void",
|
|
19478
|
+
"throw",
|
|
19479
|
+
"yield",
|
|
19480
|
+
"await"
|
|
19481
|
+
]);
|
|
19482
|
+
function substanceOf(file, text) {
|
|
19483
|
+
const syntax = syntaxOf(file);
|
|
19484
|
+
if (!syntax)
|
|
19485
|
+
return plainSubstance(text);
|
|
19486
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
19487
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
19488
|
+
return plainSubstance(text);
|
|
19489
|
+
return lex(syntax, text);
|
|
19490
|
+
}
|
|
19491
|
+
function plainSubstance(text) {
|
|
19492
|
+
return text.split(`
|
|
19493
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
19494
|
+
`);
|
|
19495
|
+
}
|
|
19496
|
+
function lex(syntax, text) {
|
|
19497
|
+
const collapse = !syntax.indented;
|
|
19498
|
+
let out = "";
|
|
19499
|
+
let lineStart = 0;
|
|
19500
|
+
let kept = 0;
|
|
19501
|
+
let pendingSpace = false;
|
|
19502
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
19503
|
+
let index = 0;
|
|
19504
|
+
if (text.startsWith("#!")) {
|
|
19505
|
+
const end = text.indexOf(`
|
|
19506
|
+
`);
|
|
19507
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
19508
|
+
kept = out.length;
|
|
19509
|
+
index = end < 0 ? text.length : end;
|
|
19510
|
+
}
|
|
19511
|
+
const emitCode = (chunk) => {
|
|
19512
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
19513
|
+
const before = out[out.length - 1];
|
|
19514
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
19515
|
+
out += " ";
|
|
19451
19516
|
}
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19517
|
+
pendingSpace = false;
|
|
19518
|
+
out += chunk;
|
|
19519
|
+
};
|
|
19520
|
+
const emitVerbatim = (chunk) => {
|
|
19521
|
+
emitCode(chunk);
|
|
19522
|
+
kept = out.length;
|
|
19523
|
+
};
|
|
19524
|
+
const newline = () => {
|
|
19525
|
+
if (!collapse) {
|
|
19526
|
+
let end = out.length;
|
|
19527
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
19528
|
+
end -= 1;
|
|
19529
|
+
out = out.slice(0, end);
|
|
19457
19530
|
}
|
|
19458
|
-
|
|
19459
|
-
|
|
19460
|
-
|
|
19461
|
-
|
|
19462
|
-
|
|
19463
|
-
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
19464
|
-
try {
|
|
19465
|
-
fs12.renameSync(lock, tomb);
|
|
19466
|
-
} catch {
|
|
19467
|
-
return false;
|
|
19531
|
+
pendingSpace = false;
|
|
19532
|
+
if (out.length > lineStart) {
|
|
19533
|
+
out += `
|
|
19534
|
+
`;
|
|
19535
|
+
lineStart = out.length;
|
|
19468
19536
|
}
|
|
19469
|
-
|
|
19470
|
-
|
|
19537
|
+
};
|
|
19538
|
+
const regexAllowed = () => {
|
|
19539
|
+
let end = out.length;
|
|
19540
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
19541
|
+
end -= 1;
|
|
19542
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
19471
19543
|
return true;
|
|
19472
|
-
|
|
19473
|
-
|
|
19474
|
-
|
|
19475
|
-
|
|
19476
|
-
|
|
19544
|
+
let start = end;
|
|
19545
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
19546
|
+
start -= 1;
|
|
19547
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
19548
|
+
};
|
|
19549
|
+
const scanString = (quote, escapes, multiline) => {
|
|
19550
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
19551
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
19552
|
+
let end = index + quote.length;
|
|
19553
|
+
while (end < text.length) {
|
|
19554
|
+
const char = text[end];
|
|
19555
|
+
if (escapes && char === "\\") {
|
|
19556
|
+
end += 2;
|
|
19557
|
+
continue;
|
|
19558
|
+
}
|
|
19559
|
+
if (text.startsWith(quote, end)) {
|
|
19560
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
19561
|
+
return false;
|
|
19562
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
19563
|
+
index = end + quote.length;
|
|
19564
|
+
return true;
|
|
19565
|
+
}
|
|
19566
|
+
if (char === `
|
|
19567
|
+
` && !multiline)
|
|
19568
|
+
return false;
|
|
19569
|
+
end += 1;
|
|
19477
19570
|
}
|
|
19478
19571
|
return false;
|
|
19479
|
-
}
|
|
19480
|
-
|
|
19481
|
-
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19487
|
-
|
|
19488
|
-
|
|
19489
|
-
|
|
19490
|
-
|
|
19491
|
-
|
|
19572
|
+
};
|
|
19573
|
+
while (index < text.length) {
|
|
19574
|
+
const frame = frames[frames.length - 1];
|
|
19575
|
+
const char = text[index];
|
|
19576
|
+
if (frame.kind === "template") {
|
|
19577
|
+
if (char === "\\") {
|
|
19578
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
19579
|
+
index += 2;
|
|
19580
|
+
} else if (char === "`") {
|
|
19581
|
+
emitVerbatim(char);
|
|
19582
|
+
frames.pop();
|
|
19583
|
+
index += 1;
|
|
19584
|
+
} else if (text.startsWith("${", index)) {
|
|
19585
|
+
emitVerbatim("${");
|
|
19586
|
+
frames.push({ kind: "code", braces: 0 });
|
|
19587
|
+
index += 2;
|
|
19588
|
+
} else {
|
|
19589
|
+
out += char;
|
|
19590
|
+
kept = out.length;
|
|
19591
|
+
index += 1;
|
|
19592
|
+
}
|
|
19492
19593
|
continue;
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
|
|
19512
|
-
|
|
19513
|
-
if (
|
|
19594
|
+
}
|
|
19595
|
+
if (char === `
|
|
19596
|
+
`) {
|
|
19597
|
+
newline();
|
|
19598
|
+
index += 1;
|
|
19599
|
+
continue;
|
|
19600
|
+
}
|
|
19601
|
+
if (SPACE.test(char)) {
|
|
19602
|
+
if (collapse)
|
|
19603
|
+
pendingSpace = true;
|
|
19604
|
+
else
|
|
19605
|
+
out += char;
|
|
19606
|
+
index += 1;
|
|
19607
|
+
continue;
|
|
19608
|
+
}
|
|
19609
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
19610
|
+
if (lineOpener) {
|
|
19611
|
+
const end = text.indexOf(`
|
|
19612
|
+
`, index);
|
|
19613
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
19614
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
19615
|
+
emitVerbatim(comment);
|
|
19616
|
+
else
|
|
19617
|
+
pendingSpace = true;
|
|
19618
|
+
index = end < 0 ? text.length : end;
|
|
19619
|
+
continue;
|
|
19620
|
+
}
|
|
19621
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
19622
|
+
if (block) {
|
|
19623
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
19624
|
+
if (end < 0)
|
|
19514
19625
|
return null;
|
|
19626
|
+
const comment = text.slice(index, end + block[1].length);
|
|
19627
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
19628
|
+
emitVerbatim(comment);
|
|
19629
|
+
else
|
|
19630
|
+
pendingSpace = true;
|
|
19631
|
+
if (comment.includes(`
|
|
19632
|
+
`))
|
|
19633
|
+
newline();
|
|
19634
|
+
index = end + block[1].length;
|
|
19515
19635
|
continue;
|
|
19516
19636
|
}
|
|
19517
|
-
|
|
19518
|
-
|
|
19519
|
-
|
|
19520
|
-
|
|
19521
|
-
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
19522
|
-
if (text) {
|
|
19523
|
-
if (start >= 0 || span === size)
|
|
19524
|
-
return text;
|
|
19525
|
-
break;
|
|
19526
|
-
}
|
|
19527
|
-
if (start < 0) {
|
|
19528
|
-
if (span === size)
|
|
19529
|
-
return null;
|
|
19530
|
-
break;
|
|
19531
|
-
}
|
|
19532
|
-
stop = start;
|
|
19637
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
19638
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
19639
|
+
return null;
|
|
19640
|
+
continue;
|
|
19533
19641
|
}
|
|
19534
|
-
if (
|
|
19535
|
-
|
|
19536
|
-
|
|
19537
|
-
|
|
19538
|
-
};
|
|
19539
|
-
var tighten = (fd) => {
|
|
19540
|
-
try {
|
|
19541
|
-
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
19542
|
-
fs12.fchmodSync(fd, 384);
|
|
19543
|
-
} catch {}
|
|
19544
|
-
};
|
|
19545
|
-
function appendLine(file, build, options) {
|
|
19546
|
-
const write = () => {
|
|
19547
|
-
const fd = fs12.openSync(file, "a+", 384);
|
|
19548
|
-
try {
|
|
19549
|
-
if (process.platform !== "win32")
|
|
19550
|
-
tighten(fd);
|
|
19551
|
-
const size = fs12.fstatSync(fd).size;
|
|
19552
|
-
let fence = "";
|
|
19553
|
-
if (size > 0) {
|
|
19554
|
-
const last = Buffer.alloc(1);
|
|
19555
|
-
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
19556
|
-
if (last[0] !== 10)
|
|
19557
|
-
fence = `
|
|
19558
|
-
`;
|
|
19559
|
-
}
|
|
19560
|
-
const line = build(lastLine(fd, size));
|
|
19561
|
-
if (!line.ok)
|
|
19562
|
-
return line;
|
|
19563
|
-
const bytes = Buffer.from(fence + line.value);
|
|
19564
|
-
const written = fs12.writeSync(fd, bytes);
|
|
19565
|
-
if (written !== bytes.length)
|
|
19566
|
-
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
19567
|
-
return { ok: true, value: undefined };
|
|
19568
|
-
} finally {
|
|
19569
|
-
fs12.closeSync(fd);
|
|
19642
|
+
if (syntax.quotes.includes(char)) {
|
|
19643
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
19644
|
+
return null;
|
|
19645
|
+
continue;
|
|
19570
19646
|
}
|
|
19571
|
-
|
|
19572
|
-
|
|
19573
|
-
|
|
19574
|
-
if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
|
|
19575
|
-
return write();
|
|
19576
|
-
const locked = withLedgerLock(file, write);
|
|
19577
|
-
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
19578
|
-
} catch (error) {
|
|
19579
|
-
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
19580
|
-
}
|
|
19581
|
-
}
|
|
19582
|
-
var sealed = (row, id, at, previous) => {
|
|
19583
|
-
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
19584
|
-
const prevHash = previous === null ? null : sha2562(previous);
|
|
19585
|
-
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
19586
|
-
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
19587
|
-
return { full, line: `${JSON.stringify(full)}
|
|
19588
|
-
` };
|
|
19589
|
-
};
|
|
19590
|
-
function appendRaw(cwd, row, options) {
|
|
19591
|
-
const file = ledgerPath(cwd);
|
|
19592
|
-
if (!file.ok)
|
|
19593
|
-
return file;
|
|
19594
|
-
const id = newId2();
|
|
19595
|
-
const at = (options.now ?? new Date).toISOString();
|
|
19596
|
-
let full = null;
|
|
19597
|
-
const written = appendLine(file.value, (previous) => {
|
|
19598
|
-
const next = sealed(row, id, at, previous);
|
|
19599
|
-
const bytes = Buffer.byteLength(next.line);
|
|
19600
|
-
if (bytes > MAX_LINE_BYTES)
|
|
19601
|
-
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>`);
|
|
19602
|
-
full = next.full;
|
|
19603
|
-
return { ok: true, value: next.line };
|
|
19604
|
-
}, options);
|
|
19605
|
-
return written.ok ? { ok: true, value: full } : written;
|
|
19606
|
-
}
|
|
19607
|
-
function observedRowBytes(row) {
|
|
19608
|
-
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
19609
|
-
return Buffer.byteLength(sample.line);
|
|
19610
|
-
}
|
|
19611
|
-
function appendObserved(cwd, row, options = {}) {
|
|
19612
|
-
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
19613
|
-
}
|
|
19614
|
-
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19615
|
-
var str2 = (value) => typeof value === "string" ? value : null;
|
|
19616
|
-
var normalizeActor = (value) => {
|
|
19617
|
-
const actor = isRecord3(value) ? value : {};
|
|
19618
|
-
return {
|
|
19619
|
-
host: str2(actor.host) ?? "unknown",
|
|
19620
|
-
session: str2(actor.session),
|
|
19621
|
-
agentId: str2(actor.agentId)
|
|
19622
|
-
};
|
|
19623
|
-
};
|
|
19624
|
-
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
19625
|
-
if (by.type !== target.type)
|
|
19626
|
-
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
19627
|
-
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
19628
|
-
return "only the session that wrote a row can supersede it";
|
|
19629
|
-
if (!onBranch(target))
|
|
19630
|
-
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
19631
|
-
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
19632
|
-
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
19633
|
-
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
19634
|
-
return "a self verdict cannot replace an independent verdict";
|
|
19635
|
-
return null;
|
|
19636
|
-
}
|
|
19637
|
-
function readLedger(cwd, options = {}) {
|
|
19638
|
-
const file = ledgerPath(cwd);
|
|
19639
|
-
if (!file.ok)
|
|
19640
|
-
return file;
|
|
19641
|
-
let text = "";
|
|
19642
|
-
let stat = null;
|
|
19643
|
-
try {
|
|
19644
|
-
stat = fs12.statSync(file.value);
|
|
19645
|
-
text = fs12.readFileSync(file.value, "utf8");
|
|
19646
|
-
} catch (error) {
|
|
19647
|
-
if (error.code === "ENOENT")
|
|
19648
|
-
return {
|
|
19649
|
-
ok: true,
|
|
19650
|
-
value: {
|
|
19651
|
-
path: file.value,
|
|
19652
|
-
rows: [],
|
|
19653
|
-
skipped: 0,
|
|
19654
|
-
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
19655
|
-
}
|
|
19656
|
-
};
|
|
19657
|
-
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
19658
|
-
}
|
|
19659
|
-
const rows = [];
|
|
19660
|
-
let skipped = 0;
|
|
19661
|
-
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
19662
|
-
const earlier = new Set([null]);
|
|
19663
|
-
let chainStarted = false;
|
|
19664
|
-
const ids = new Set;
|
|
19665
|
-
for (const raw of text.split(`
|
|
19666
|
-
`)) {
|
|
19667
|
-
const trimmed = raw.trim();
|
|
19668
|
-
if (!trimmed)
|
|
19647
|
+
if (syntax.literal?.includes(char)) {
|
|
19648
|
+
if (!scanString(char, false, false))
|
|
19649
|
+
return null;
|
|
19669
19650
|
continue;
|
|
19670
|
-
|
|
19671
|
-
if (
|
|
19672
|
-
|
|
19673
|
-
|
|
19674
|
-
try {
|
|
19675
|
-
parsed = JSON.parse(trimmed);
|
|
19676
|
-
} catch {
|
|
19677
|
-
skipped += 1;
|
|
19651
|
+
}
|
|
19652
|
+
if (syntax.rawBacktick && char === "`") {
|
|
19653
|
+
if (!scanString("`", false, true))
|
|
19654
|
+
return null;
|
|
19678
19655
|
continue;
|
|
19679
19656
|
}
|
|
19680
|
-
if (
|
|
19681
|
-
|
|
19657
|
+
if (syntax.js && char === "`") {
|
|
19658
|
+
emitVerbatim(char);
|
|
19659
|
+
frames.push({ kind: "template" });
|
|
19660
|
+
index += 1;
|
|
19682
19661
|
continue;
|
|
19683
19662
|
}
|
|
19684
|
-
|
|
19685
|
-
|
|
19686
|
-
if (
|
|
19687
|
-
|
|
19688
|
-
|
|
19689
|
-
|
|
19690
|
-
|
|
19691
|
-
|
|
19692
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
19703
|
-
|
|
19704
|
-
|
|
19705
|
-
|
|
19706
|
-
|
|
19707
|
-
|
|
19708
|
-
|
|
19709
|
-
|
|
19710
|
-
|
|
19711
|
-
actor: normalizeActor(parsed.actor),
|
|
19712
|
-
branch: str2(parsed.branch),
|
|
19713
|
-
head: str2(parsed.head),
|
|
19714
|
-
base: str2(parsed.base),
|
|
19715
|
-
baseSha: str2(parsed.baseSha),
|
|
19716
|
-
patchId: str2(parsed.patchId),
|
|
19717
|
-
diffHash: str2(parsed.diffHash),
|
|
19718
|
-
tree: str2(parsed.tree),
|
|
19719
|
-
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
19720
|
-
...pr === undefined ? {} : { pr },
|
|
19721
|
-
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
19722
|
-
seq: rows.length + 1,
|
|
19723
|
-
superseded: false,
|
|
19724
|
-
supersedeIgnored: false,
|
|
19725
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
19726
|
-
...reason ? { integrityReason: reason } : {}
|
|
19727
|
-
};
|
|
19728
|
-
rows.push(row);
|
|
19729
|
-
if (!options.integrity)
|
|
19663
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
19664
|
+
return null;
|
|
19665
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
19666
|
+
const lineEnd = text.indexOf(`
|
|
19667
|
+
`, index);
|
|
19668
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
19669
|
+
return null;
|
|
19670
|
+
}
|
|
19671
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
19672
|
+
let end = index + 1;
|
|
19673
|
+
let inClass = false;
|
|
19674
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
19675
|
+
if (text[end] === `
|
|
19676
|
+
`)
|
|
19677
|
+
return null;
|
|
19678
|
+
if (text[end] === "\\")
|
|
19679
|
+
end += 1;
|
|
19680
|
+
else if (text[end] === "[")
|
|
19681
|
+
inClass = true;
|
|
19682
|
+
else if (text[end] === "]")
|
|
19683
|
+
inClass = false;
|
|
19684
|
+
end += 1;
|
|
19685
|
+
}
|
|
19686
|
+
if (end >= text.length)
|
|
19687
|
+
return null;
|
|
19688
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
19689
|
+
index = end + 1;
|
|
19730
19690
|
continue;
|
|
19731
|
-
|
|
19732
|
-
|
|
19733
|
-
|
|
19734
|
-
|
|
19735
|
-
|
|
19736
|
-
|
|
19737
|
-
|
|
19738
|
-
|
|
19739
|
-
|
|
19740
|
-
|
|
19741
|
-
|
|
19742
|
-
|
|
19691
|
+
}
|
|
19692
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
19693
|
+
if (char === "{")
|
|
19694
|
+
frame.braces += 1;
|
|
19695
|
+
else if (char === "}") {
|
|
19696
|
+
if (frame.braces === 0) {
|
|
19697
|
+
emitVerbatim(char);
|
|
19698
|
+
frames.pop();
|
|
19699
|
+
index += 1;
|
|
19700
|
+
continue;
|
|
19701
|
+
}
|
|
19702
|
+
frame.braces -= 1;
|
|
19703
|
+
}
|
|
19704
|
+
}
|
|
19705
|
+
emitCode(char);
|
|
19706
|
+
index += 1;
|
|
19743
19707
|
}
|
|
19744
|
-
|
|
19745
|
-
|
|
19746
|
-
|
|
19747
|
-
|
|
19748
|
-
|
|
19749
|
-
|
|
19750
|
-
|
|
19751
|
-
const
|
|
19752
|
-
|
|
19753
|
-
|
|
19754
|
-
if (
|
|
19755
|
-
|
|
19756
|
-
else if (row.supersedes)
|
|
19757
|
-
row.supersedeIgnored = true;
|
|
19758
|
-
if (!byId.has(row.id))
|
|
19759
|
-
byId.set(row.id, row);
|
|
19708
|
+
if (frames.length !== 1)
|
|
19709
|
+
return null;
|
|
19710
|
+
newline();
|
|
19711
|
+
return out;
|
|
19712
|
+
}
|
|
19713
|
+
function unbalanced(body) {
|
|
19714
|
+
let depth = 0;
|
|
19715
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
19716
|
+
if (part === "{")
|
|
19717
|
+
depth += 1;
|
|
19718
|
+
else if (part === "}")
|
|
19719
|
+
depth = Math.max(0, depth - 1);
|
|
19760
19720
|
}
|
|
19761
|
-
|
|
19762
|
-
tamperCache.set(file.value, {
|
|
19763
|
-
size: stat.size,
|
|
19764
|
-
stamp: fileStamp(stat),
|
|
19765
|
-
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
19766
|
-
});
|
|
19767
|
-
return {
|
|
19768
|
-
ok: true,
|
|
19769
|
-
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
19770
|
-
};
|
|
19721
|
+
return depth > 0;
|
|
19771
19722
|
}
|
|
19772
|
-
|
|
19773
|
-
|
|
19774
|
-
|
|
19775
|
-
|
|
19776
|
-
|
|
19777
|
-
if (
|
|
19778
|
-
return
|
|
19723
|
+
function hasOpaqueLineBreak(text) {
|
|
19724
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
19725
|
+
}
|
|
19726
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
19727
|
+
function textOf(bytes) {
|
|
19728
|
+
if (bytes.includes(0))
|
|
19729
|
+
return null;
|
|
19779
19730
|
try {
|
|
19780
|
-
|
|
19781
|
-
const cached = tamperCache.get(file.value);
|
|
19782
|
-
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
19783
|
-
return cached.breaks;
|
|
19731
|
+
return decoder.decode(bytes);
|
|
19784
19732
|
} catch {
|
|
19785
|
-
return
|
|
19733
|
+
return null;
|
|
19786
19734
|
}
|
|
19787
|
-
const read = readLedger(cwd, { integrity: true });
|
|
19788
|
-
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
19789
|
-
}
|
|
19790
|
-
function tamperedUnblock(cwd) {
|
|
19791
|
-
const file = ledgerPath(cwd);
|
|
19792
|
-
const where = file.ok ? file.value : "the ledger file";
|
|
19793
|
-
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`;
|
|
19794
19735
|
}
|
|
19795
|
-
|
|
19796
|
-
|
|
19797
|
-
|
|
19798
|
-
|
|
19799
|
-
|
|
19800
|
-
|
|
19801
|
-
|
|
19736
|
+
function cosmeticChange(file, before, after) {
|
|
19737
|
+
const left = textOf(before);
|
|
19738
|
+
const right = textOf(after);
|
|
19739
|
+
if (left === null || right === null)
|
|
19740
|
+
return false;
|
|
19741
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
19742
|
+
return false;
|
|
19743
|
+
const a = substanceOf(file, left);
|
|
19744
|
+
return a !== null && a === substanceOf(file, right);
|
|
19802
19745
|
}
|
|
19803
|
-
|
|
19804
|
-
|
|
19805
|
-
|
|
19806
|
-
|
|
19807
|
-
|
|
19808
|
-
|
|
19809
|
-
|
|
19810
|
-
|
|
19811
|
-
|
|
19812
|
-
|
|
19746
|
+
|
|
19747
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
19748
|
+
var LANE_THRESHOLDS = {
|
|
19749
|
+
selfMaxLines: 100,
|
|
19750
|
+
selfMaxFiles: 5,
|
|
19751
|
+
fullMinLines: 500
|
|
19752
|
+
};
|
|
19753
|
+
var RISK_TOKENS = new Set([
|
|
19754
|
+
"auth",
|
|
19755
|
+
"authn",
|
|
19756
|
+
"authz",
|
|
19757
|
+
"oauth",
|
|
19758
|
+
"security",
|
|
19759
|
+
"permission",
|
|
19760
|
+
"permissions",
|
|
19761
|
+
"secret",
|
|
19762
|
+
"secrets",
|
|
19763
|
+
"crypto",
|
|
19764
|
+
"gate",
|
|
19765
|
+
"gates",
|
|
19766
|
+
"policy",
|
|
19767
|
+
"policies",
|
|
19768
|
+
"hook",
|
|
19769
|
+
"hooks",
|
|
19770
|
+
"husky",
|
|
19771
|
+
"lefthook",
|
|
19772
|
+
"release",
|
|
19773
|
+
"releases",
|
|
19774
|
+
"migration",
|
|
19775
|
+
"migrations",
|
|
19776
|
+
"migrate",
|
|
19777
|
+
"workflows"
|
|
19778
|
+
]);
|
|
19779
|
+
var RISK_FILES = new Set([
|
|
19780
|
+
".gitlab-ci.yml",
|
|
19781
|
+
"Jenkinsfile",
|
|
19782
|
+
"azure-pipelines.yml",
|
|
19783
|
+
".pre-commit-config.yaml",
|
|
19784
|
+
".workit/risk-paths"
|
|
19785
|
+
]);
|
|
19786
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
19787
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
19788
|
+
var LOCKFILES = new Set([
|
|
19789
|
+
"bun.lock",
|
|
19790
|
+
"bun.lockb",
|
|
19791
|
+
"package-lock.json",
|
|
19792
|
+
"npm-shrinkwrap.json",
|
|
19793
|
+
"yarn.lock",
|
|
19794
|
+
"pnpm-lock.yaml",
|
|
19795
|
+
"Cargo.lock",
|
|
19796
|
+
"go.sum",
|
|
19797
|
+
"poetry.lock",
|
|
19798
|
+
"uv.lock",
|
|
19799
|
+
"Pipfile.lock",
|
|
19800
|
+
"Gemfile.lock",
|
|
19801
|
+
"composer.lock"
|
|
19802
|
+
]);
|
|
19803
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
19804
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
19805
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
19806
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
19807
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
19808
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
19809
|
+
function kindOf(file) {
|
|
19810
|
+
if (file.binary)
|
|
19811
|
+
return "behavior";
|
|
19812
|
+
if (file.generated)
|
|
19813
|
+
return "generated";
|
|
19814
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
19815
|
+
return "lockfile";
|
|
19816
|
+
if (isDoc(file.path))
|
|
19817
|
+
return "doc";
|
|
19818
|
+
if (file.cosmetic)
|
|
19819
|
+
return "cosmetic";
|
|
19820
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
19813
19821
|
}
|
|
19814
|
-
var
|
|
19815
|
-
function
|
|
19816
|
-
|
|
19817
|
-
|
|
19818
|
-
|
|
19819
|
-
|
|
19820
|
-
|
|
19821
|
-
"
|
|
19822
|
-
|
|
19823
|
-
|
|
19824
|
-
|
|
19825
|
-
|
|
19826
|
-
|
|
19827
|
-
|
|
19828
|
-
|
|
19829
|
-
|
|
19830
|
-
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
19831
|
-
if (!Number.isFinite(seconds))
|
|
19832
|
-
continue;
|
|
19833
|
-
since = seconds * 1000;
|
|
19834
|
-
const match = RENAMED.exec(subject);
|
|
19835
|
-
if (!match || match[1] === branch)
|
|
19836
|
-
continue;
|
|
19837
|
-
const until = (seconds + 1) * 1000;
|
|
19838
|
-
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
19822
|
+
var globCache = new Map;
|
|
19823
|
+
function globMatch(pattern, file) {
|
|
19824
|
+
let regex = globCache.get(pattern);
|
|
19825
|
+
if (!regex) {
|
|
19826
|
+
let source = "";
|
|
19827
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
19828
|
+
const char = pattern[index];
|
|
19829
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
19830
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
19831
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
19832
|
+
} else if (char === "*")
|
|
19833
|
+
source += "[^/]*";
|
|
19834
|
+
else if (char === "?")
|
|
19835
|
+
source += "[^/]";
|
|
19836
|
+
else
|
|
19837
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
19839
19838
|
}
|
|
19839
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
19840
|
+
globCache.set(pattern, regex);
|
|
19840
19841
|
}
|
|
19841
|
-
return (
|
|
19842
|
-
if (row.branch === branch)
|
|
19843
|
-
return true;
|
|
19844
|
-
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
19845
|
-
if (until === undefined)
|
|
19846
|
-
return false;
|
|
19847
|
-
const at = Date.parse(row.at);
|
|
19848
|
-
return at >= since && at <= until;
|
|
19849
|
-
};
|
|
19850
|
-
}
|
|
19851
|
-
var renameAwareBranch = (scopeOf, branch) => {
|
|
19852
|
-
return (target) => {
|
|
19853
|
-
const own = typeof branch === "string" ? branch : null;
|
|
19854
|
-
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
19855
|
-
if (own === theirs)
|
|
19856
|
-
return true;
|
|
19857
|
-
if (own === null || theirs === null || typeof target.at !== "string")
|
|
19858
|
-
return false;
|
|
19859
|
-
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
19860
|
-
};
|
|
19861
|
-
};
|
|
19862
|
-
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
19863
|
-
const sessions = new Set;
|
|
19864
|
-
for (const row of rows) {
|
|
19865
|
-
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
19866
|
-
continue;
|
|
19867
|
-
const session = str2(row.session) ?? row.actor.session;
|
|
19868
|
-
if (session)
|
|
19869
|
-
sessions.add(session);
|
|
19870
|
-
}
|
|
19871
|
-
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
19872
|
-
sessions.add(session);
|
|
19873
|
-
return sessions;
|
|
19842
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
19874
19843
|
}
|
|
19875
|
-
|
|
19876
|
-
|
|
19877
|
-
if (authors.has(session))
|
|
19844
|
+
function isRiskPath(file, extra = []) {
|
|
19845
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
19878
19846
|
return true;
|
|
19879
|
-
|
|
19880
|
-
|
|
19881
|
-
|
|
19882
|
-
|
|
19883
|
-
|
|
19884
|
-
var TRUST_REASONS = new Set([
|
|
19885
|
-
"unsigned",
|
|
19886
|
-
"hash_mismatch",
|
|
19887
|
-
"broken_link",
|
|
19888
|
-
"duplicate_id",
|
|
19889
|
-
"unissued_session",
|
|
19890
|
-
"unbound_session"
|
|
19891
|
-
]);
|
|
19892
|
-
var SHELL_VOUCHED_HOSTS = new Set([
|
|
19893
|
-
"cli",
|
|
19894
|
-
"opencode",
|
|
19895
|
-
"pi",
|
|
19896
|
-
"codex_cli",
|
|
19897
|
-
"codex_desktop"
|
|
19898
|
-
]);
|
|
19899
|
-
function sessionTrust(cwd, row) {
|
|
19900
|
-
const session = row.actor.session;
|
|
19901
|
-
if (!session || row.self === true)
|
|
19902
|
-
return null;
|
|
19903
|
-
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
19904
|
-
const host = row.actor.host;
|
|
19905
|
-
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
19906
|
-
if (!issuedOnly)
|
|
19907
|
-
return null;
|
|
19908
|
-
const binding = issuedBinding(cwd, session);
|
|
19909
|
-
if (binding === null)
|
|
19910
|
-
return "unissued_session";
|
|
19911
|
-
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
19912
|
-
}
|
|
19913
|
-
function trustReasons(cwd, row) {
|
|
19914
|
-
if (row.integrity === "unverified")
|
|
19915
|
-
return [row.integrityReason ?? "unsigned"];
|
|
19916
|
-
if (row.integrity !== "verified")
|
|
19917
|
-
return ["unsigned"];
|
|
19918
|
-
const session = sessionTrust(cwd, row);
|
|
19919
|
-
return session ? [session] : [];
|
|
19847
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
19848
|
+
return true;
|
|
19849
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
19850
|
+
return false;
|
|
19851
|
+
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)));
|
|
19920
19852
|
}
|
|
19921
|
-
|
|
19922
|
-
|
|
19923
|
-
|
|
19924
|
-
const
|
|
19925
|
-
const
|
|
19926
|
-
|
|
19927
|
-
|
|
19928
|
-
|
|
19929
|
-
|
|
19930
|
-
|
|
19853
|
+
var parseRiskPaths = (text) => text.split(`
|
|
19854
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
19855
|
+
function computeLane(input) {
|
|
19856
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
19857
|
+
const risky = new Set(riskFiles);
|
|
19858
|
+
const kinds = input.files.map((file) => ({
|
|
19859
|
+
file,
|
|
19860
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
19861
|
+
}));
|
|
19862
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
19863
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
19864
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
19865
|
+
const result = (lane, reasons) => ({
|
|
19866
|
+
lane,
|
|
19867
|
+
reasons,
|
|
19868
|
+
lines,
|
|
19869
|
+
files: behavior.length,
|
|
19870
|
+
riskFiles
|
|
19931
19871
|
});
|
|
19872
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
19873
|
+
if (input.riskTier === "high")
|
|
19874
|
+
return result("full", ["the task judgment records high risk"]);
|
|
19875
|
+
if (riskFiles.length)
|
|
19876
|
+
return result("full", [
|
|
19877
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
19878
|
+
]);
|
|
19879
|
+
if (!behavior.length)
|
|
19880
|
+
return result("none", [
|
|
19881
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
19882
|
+
]);
|
|
19883
|
+
if (lines >= fullMinLines)
|
|
19884
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
19885
|
+
if (lines > selfMaxLines)
|
|
19886
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
19887
|
+
if (behavior.length > selfMaxFiles)
|
|
19888
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
19889
|
+
if (opaque.length)
|
|
19890
|
+
return result("focused", [
|
|
19891
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
19892
|
+
]);
|
|
19893
|
+
return result("self", [
|
|
19894
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
19895
|
+
]);
|
|
19932
19896
|
}
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
|
|
19938
|
-
|
|
19939
|
-
|
|
19940
|
-
|
|
19941
|
-
|
|
19942
|
-
|
|
19943
|
-
|
|
19944
|
-
|
|
19945
|
-
|
|
19946
|
-
|
|
19947
|
-
if (!effective) {
|
|
19948
|
-
effective = row;
|
|
19949
|
-
continue;
|
|
19950
|
-
}
|
|
19951
|
-
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
19952
|
-
continue;
|
|
19953
|
-
const rowIndependent = reasonsOf(row).length === 0;
|
|
19954
|
-
const effIndependent = reasonsOf(effective).length === 0;
|
|
19955
|
-
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
19956
|
-
continue;
|
|
19957
|
-
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
19958
|
-
continue;
|
|
19959
|
-
effective = row;
|
|
19897
|
+
function untrackedStat(cwd, file) {
|
|
19898
|
+
try {
|
|
19899
|
+
const bytes = readFileSync4(path14.join(cwd, file));
|
|
19900
|
+
if (bytes.includes(0))
|
|
19901
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
19902
|
+
const text = bytes.toString("utf8");
|
|
19903
|
+
if (hasOpaqueLineBreak(text))
|
|
19904
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
19905
|
+
const lines = text.split(`
|
|
19906
|
+
`).length - (text.endsWith(`
|
|
19907
|
+
`) ? 1 : 0);
|
|
19908
|
+
return { path: file, added: lines, deleted: 0 };
|
|
19909
|
+
} catch {
|
|
19910
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
19960
19911
|
}
|
|
19961
|
-
return effective;
|
|
19962
19912
|
}
|
|
19963
|
-
|
|
19964
|
-
|
|
19965
|
-
|
|
19966
|
-
const
|
|
19967
|
-
|
|
19968
|
-
|
|
19969
|
-
|
|
19970
|
-
const
|
|
19971
|
-
|
|
19972
|
-
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
19976
|
-
|
|
19977
|
-
|
|
19978
|
-
|
|
19979
|
-
|
|
19913
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
19914
|
+
var ABSENT = /^0+$/u;
|
|
19915
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
19916
|
+
const raw = diffRaw(cwd, fork, head);
|
|
19917
|
+
if (!raw)
|
|
19918
|
+
return null;
|
|
19919
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
19920
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
19921
|
+
if (!blobs)
|
|
19922
|
+
return null;
|
|
19923
|
+
const out = new Set;
|
|
19924
|
+
const opaque = new Set;
|
|
19925
|
+
for (const entry of candidates) {
|
|
19926
|
+
const before = blobs.get(entry.oldBlob);
|
|
19927
|
+
let after = blobs.get(entry.newBlob);
|
|
19928
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
19929
|
+
try {
|
|
19930
|
+
after = readFileSync4(path14.join(cwd, entry.path));
|
|
19931
|
+
} catch {
|
|
19932
|
+
after = undefined;
|
|
19980
19933
|
}
|
|
19934
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
19935
|
+
out.add(entry.path);
|
|
19936
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
19937
|
+
opaque.add(entry.path);
|
|
19981
19938
|
}
|
|
19982
|
-
return
|
|
19939
|
+
return { cosmetic: out, opaque };
|
|
19983
19940
|
}
|
|
19984
|
-
function
|
|
19985
|
-
const
|
|
19986
|
-
const
|
|
19987
|
-
|
|
19988
|
-
|
|
19989
|
-
const
|
|
19990
|
-
const
|
|
19991
|
-
|
|
19992
|
-
|
|
19993
|
-
|
|
19994
|
-
|
|
19995
|
-
|
|
19996
|
-
|
|
19997
|
-
|
|
19998
|
-
|
|
19999
|
-
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
|
|
20003
|
-
|
|
20004
|
-
|
|
20005
|
-
|
|
20006
|
-
|
|
20007
|
-
|
|
20008
|
-
|
|
20009
|
-
|
|
20010
|
-
|
|
20011
|
-
|
|
20012
|
-
|
|
20013
|
-
|
|
20014
|
-
|
|
20015
|
-
|
|
20016
|
-
|
|
20017
|
-
|
|
20018
|
-
|
|
20019
|
-
|
|
20020
|
-
|
|
20021
|
-
|
|
20022
|
-
|
|
20023
|
-
|
|
20024
|
-
const
|
|
20025
|
-
const
|
|
20026
|
-
|
|
20027
|
-
|
|
20028
|
-
|
|
20029
|
-
|
|
20030
|
-
|
|
20031
|
-
|
|
20032
|
-
|
|
20033
|
-
|
|
20034
|
-
|
|
20035
|
-
|
|
20036
|
-
};
|
|
20037
|
-
const
|
|
20038
|
-
|
|
20039
|
-
|
|
20040
|
-
|
|
20041
|
-
const
|
|
20042
|
-
|
|
20043
|
-
|
|
20044
|
-
|
|
20045
|
-
|
|
20046
|
-
|
|
20047
|
-
|
|
20048
|
-
|
|
20049
|
-
|
|
20050
|
-
|
|
20051
|
-
|
|
20052
|
-
|
|
20053
|
-
|
|
20054
|
-
|
|
20055
|
-
|
|
20056
|
-
|
|
20057
|
-
|
|
20058
|
-
|
|
20059
|
-
|
|
20060
|
-
|
|
20061
|
-
|
|
20062
|
-
|
|
20063
|
-
|
|
20064
|
-
|
|
20065
|
-
|
|
20066
|
-
accepted: reasons.length === 0,
|
|
20067
|
-
reasons,
|
|
20068
|
-
verdict: row
|
|
20069
|
-
});
|
|
20070
|
-
if (reasons.length === 0) {
|
|
20071
|
-
for (const later of list)
|
|
20072
|
-
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
20073
|
-
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`);
|
|
20074
|
-
}
|
|
20075
|
-
}
|
|
20076
|
-
for (const row of rows)
|
|
20077
|
-
if (row.integrity === "unverified" && scope(row))
|
|
20078
|
-
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)`);
|
|
20079
|
-
const tampered = ledgerTampering(cwd);
|
|
20080
|
-
if (tampered.length) {
|
|
20081
|
-
warnings.push(tamperedWarning(cwd, tampered));
|
|
20082
|
-
for (const entry of verdicts) {
|
|
20083
|
-
entry.accepted = false;
|
|
20084
|
-
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
20085
|
-
}
|
|
20086
|
-
}
|
|
20087
|
-
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
20088
|
-
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
20089
|
-
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
20090
|
-
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
20091
|
-
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
20092
|
-
accepted: false,
|
|
20093
|
-
verdict: failing?.verdict ?? null,
|
|
20094
|
-
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
20095
|
-
};
|
|
20096
|
-
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))));
|
|
20097
|
-
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
20098
|
-
return {
|
|
20099
|
-
branch,
|
|
20100
|
-
head,
|
|
20101
|
-
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
20102
|
-
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
20103
|
-
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
20104
|
-
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
20105
|
-
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
20106
|
-
accepted,
|
|
20107
|
-
verdicts,
|
|
20108
|
-
authors: [...authors].toSorted(),
|
|
20109
|
-
warnings
|
|
19941
|
+
function generatedAt(cwd, fork, paths) {
|
|
19942
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
19943
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
19944
|
+
if (!files || !blobs)
|
|
19945
|
+
return new Set;
|
|
19946
|
+
const texts = new Map;
|
|
19947
|
+
for (const [file, id] of files) {
|
|
19948
|
+
const bytes = blobs.get(id);
|
|
19949
|
+
if (bytes)
|
|
19950
|
+
texts.set(file, bytes.toString("utf8"));
|
|
19951
|
+
}
|
|
19952
|
+
return generatedByAttributes(texts, paths);
|
|
19953
|
+
}
|
|
19954
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
19955
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
19956
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
19957
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
19958
|
+
if (!stats || !cosmetic)
|
|
19959
|
+
return null;
|
|
19960
|
+
const files = [
|
|
19961
|
+
...stats.map((entry) => ({
|
|
19962
|
+
...entry,
|
|
19963
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
19964
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
19965
|
+
})),
|
|
19966
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
19967
|
+
];
|
|
19968
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
19969
|
+
return files.map((entry) => ({
|
|
19970
|
+
...entry,
|
|
19971
|
+
generated: generated.has(entry.path)
|
|
19972
|
+
}));
|
|
19973
|
+
}
|
|
19974
|
+
function driftFiles(cwd, range) {
|
|
19975
|
+
if (range.fromFork === range.toFork)
|
|
19976
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
19977
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
19978
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
19979
|
+
if (!drifted || !stats)
|
|
19980
|
+
return null;
|
|
19981
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
19982
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
19983
|
+
return drifted.map((file) => ({
|
|
19984
|
+
path: file,
|
|
19985
|
+
added: byPath.get(file)?.added ?? 0,
|
|
19986
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
19987
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
19988
|
+
cosmetic: false,
|
|
19989
|
+
generated: generated.has(file)
|
|
19990
|
+
}));
|
|
19991
|
+
}
|
|
19992
|
+
function branchLane(cwd, options) {
|
|
19993
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
19994
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
19995
|
+
const head = options.workingTree ? null : options.head;
|
|
19996
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
19997
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
19998
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
19999
|
+
if (!fork || !files) {
|
|
20000
|
+
const high = riskTier === "high";
|
|
20001
|
+
return {
|
|
20002
|
+
lane: high ? "full" : "focused",
|
|
20003
|
+
reasons: [
|
|
20004
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
20005
|
+
...high ? ["the task judgment records high risk"] : []
|
|
20006
|
+
],
|
|
20007
|
+
lines: 0,
|
|
20008
|
+
files: 0,
|
|
20009
|
+
riskFiles: [],
|
|
20010
|
+
base,
|
|
20011
|
+
head: options.head
|
|
20012
|
+
};
|
|
20013
|
+
}
|
|
20014
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
20015
|
+
return {
|
|
20016
|
+
...computeLane({
|
|
20017
|
+
files,
|
|
20018
|
+
riskTier,
|
|
20019
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
20020
|
+
}),
|
|
20021
|
+
base,
|
|
20022
|
+
head: options.head
|
|
20110
20023
|
};
|
|
20111
20024
|
}
|
|
20112
|
-
var
|
|
20113
|
-
"check",
|
|
20114
|
-
"merge.unverified",
|
|
20115
|
-
"policy.judged",
|
|
20116
|
-
...PR_ROW_TYPES
|
|
20117
|
-
]);
|
|
20118
|
-
|
|
20119
|
-
// packages/workit-core/src/core/policy/lane.ts
|
|
20120
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
20121
|
-
import path16 from "node:path";
|
|
20025
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
20122
20026
|
|
|
20123
|
-
// packages/workit-core/src/core/
|
|
20124
|
-
|
|
20125
|
-
|
|
20126
|
-
|
|
20127
|
-
|
|
20128
|
-
|
|
20129
|
-
|
|
20130
|
-
|
|
20027
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
20028
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
20029
|
+
function verdictDrift(cwd, input) {
|
|
20030
|
+
const since = input.from;
|
|
20031
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
20032
|
+
carries: false,
|
|
20033
|
+
since,
|
|
20034
|
+
files,
|
|
20035
|
+
focused,
|
|
20036
|
+
reason
|
|
20037
|
+
});
|
|
20038
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
20039
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
20040
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
20041
|
+
if (!fromFork || !toFork || !files)
|
|
20042
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
20043
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
20044
|
+
const lane = computeLane({ files, riskPaths });
|
|
20045
|
+
const paths = files.map((file) => file.path);
|
|
20046
|
+
if (lane.lane === "none")
|
|
20047
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
20048
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
20049
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
20050
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
20131
20051
|
}
|
|
20132
20052
|
|
|
20133
|
-
// packages/workit-core/src/
|
|
20134
|
-
|
|
20135
|
-
|
|
20136
|
-
|
|
20137
|
-
|
|
20138
|
-
|
|
20139
|
-
|
|
20140
|
-
|
|
20141
|
-
|
|
20142
|
-
|
|
20143
|
-
|
|
20144
|
-
|
|
20145
|
-
|
|
20146
|
-
|
|
20147
|
-
|
|
20148
|
-
|
|
20149
|
-
|
|
20150
|
-
|
|
20151
|
-
|
|
20053
|
+
// packages/workit-core/src/ledger.ts
|
|
20054
|
+
var LEDGER_VERSION = 1;
|
|
20055
|
+
var MAX_LINE_BYTES = 4096;
|
|
20056
|
+
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
20057
|
+
var FAILING = new Set(["failed", "blocked"]);
|
|
20058
|
+
var STRONG_RESULTS = new Set(["verified", "tests-verified"]);
|
|
20059
|
+
var PR_ROW_TYPES = new Set([
|
|
20060
|
+
"pr.created",
|
|
20061
|
+
"pr.status",
|
|
20062
|
+
"pr.merged",
|
|
20063
|
+
"pr.linked"
|
|
20064
|
+
]);
|
|
20065
|
+
var err = (code, error, unblock) => ({
|
|
20066
|
+
ok: false,
|
|
20067
|
+
code,
|
|
20068
|
+
error,
|
|
20069
|
+
...unblock ? { unblock } : {}
|
|
20070
|
+
});
|
|
20071
|
+
var gitRun2 = (cwd, args, timeoutMs = GIT_TIMEOUTS.local) => {
|
|
20072
|
+
const run = spawnSync9("git", args, {
|
|
20152
20073
|
cwd,
|
|
20153
|
-
encoding: "utf8"
|
|
20074
|
+
encoding: "utf8",
|
|
20075
|
+
timeout: timeoutMs,
|
|
20076
|
+
killSignal: "SIGKILL",
|
|
20077
|
+
windowsHide: true,
|
|
20078
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
20079
|
+
env: {
|
|
20080
|
+
...process.env,
|
|
20081
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
20082
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
20083
|
+
LC_ALL: "C",
|
|
20084
|
+
LANGUAGE: "C"
|
|
20085
|
+
}
|
|
20154
20086
|
});
|
|
20155
|
-
|
|
20156
|
-
|
|
20157
|
-
|
|
20158
|
-
|
|
20087
|
+
return {
|
|
20088
|
+
ok: run.status === 0,
|
|
20089
|
+
stdout: run.stdout ?? "",
|
|
20090
|
+
stderr: run.error ? String(run.error.message) : run.stderr ?? ""
|
|
20091
|
+
};
|
|
20159
20092
|
};
|
|
20160
|
-
|
|
20161
|
-
|
|
20162
|
-
|
|
20163
|
-
|
|
20164
|
-
|
|
20165
|
-
var
|
|
20166
|
-
|
|
20167
|
-
|
|
20168
|
-
var COMMIT_TIMEOUT_MS = 10 * 60000;
|
|
20169
|
-
|
|
20170
|
-
// packages/workit-core/src/core/policy/attributes.ts
|
|
20171
|
-
function patternRegex(dir, pattern) {
|
|
20172
|
-
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
20093
|
+
var git4 = (cwd, args) => {
|
|
20094
|
+
const run = gitRun2(cwd, args);
|
|
20095
|
+
const value = run.ok ? run.stdout.trim() : "";
|
|
20096
|
+
return value ? value : null;
|
|
20097
|
+
};
|
|
20098
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
20099
|
+
function resolveCommit(cwd, ref) {
|
|
20100
|
+
if (!safeRef2(ref))
|
|
20173
20101
|
return null;
|
|
20174
|
-
|
|
20175
|
-
|
|
20176
|
-
|
|
20177
|
-
|
|
20178
|
-
const char = body[index];
|
|
20179
|
-
if (char === "*" && body[index + 1] === "*") {
|
|
20180
|
-
const slash = body[index + 2] === "/";
|
|
20181
|
-
source += slash ? "(?:.*/)?" : ".*";
|
|
20182
|
-
index += slash ? 2 : 1;
|
|
20183
|
-
} else if (char === "*")
|
|
20184
|
-
source += "[^/]*";
|
|
20185
|
-
else if (char === "?")
|
|
20186
|
-
source += "[^/]";
|
|
20187
|
-
else if (char === "[") {
|
|
20188
|
-
const end = body.indexOf("]", index + 2);
|
|
20189
|
-
if (end < 0)
|
|
20190
|
-
return null;
|
|
20191
|
-
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
20192
|
-
source += `[${set}]`;
|
|
20193
|
-
index = end;
|
|
20194
|
-
} else if (char === "\\" && index + 1 < body.length) {
|
|
20195
|
-
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
20196
|
-
index += 1;
|
|
20197
|
-
} else
|
|
20198
|
-
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
20199
|
-
}
|
|
20200
|
-
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
20201
|
-
try {
|
|
20202
|
-
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
20203
|
-
} catch {
|
|
20102
|
+
return git4(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
20103
|
+
}
|
|
20104
|
+
function branchHead(cwd, branch) {
|
|
20105
|
+
if (!safeRef2(branch))
|
|
20204
20106
|
return null;
|
|
20205
|
-
}
|
|
20107
|
+
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
20206
20108
|
}
|
|
20207
|
-
function
|
|
20208
|
-
|
|
20209
|
-
|
|
20109
|
+
function diffHash(cwd, base, head) {
|
|
20110
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
20111
|
+
return null;
|
|
20112
|
+
const diff = gitRun2(cwd, [
|
|
20113
|
+
"diff",
|
|
20114
|
+
"-U3",
|
|
20115
|
+
"--diff-algorithm=myers",
|
|
20116
|
+
"--indent-heuristic",
|
|
20117
|
+
"--no-color",
|
|
20118
|
+
"--no-ext-diff",
|
|
20119
|
+
"--no-textconv",
|
|
20120
|
+
"--no-renames",
|
|
20121
|
+
"--full-index",
|
|
20122
|
+
"--binary",
|
|
20123
|
+
"--src-prefix=a/",
|
|
20124
|
+
"--dst-prefix=b/",
|
|
20125
|
+
`${base}...${head}`,
|
|
20126
|
+
"--"
|
|
20127
|
+
], GIT_TIMEOUTS.worktree);
|
|
20128
|
+
if (!diff.ok || !diff.stdout)
|
|
20129
|
+
return null;
|
|
20130
|
+
const hash = createHash9("sha256");
|
|
20131
|
+
for (const line of diff.stdout.split(`
|
|
20210
20132
|
`)) {
|
|
20211
|
-
|
|
20212
|
-
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
20133
|
+
if (line.startsWith("index "))
|
|
20213
20134
|
continue;
|
|
20214
|
-
|
|
20215
|
-
|
|
20216
|
-
|
|
20217
|
-
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
20218
|
-
generated = true;
|
|
20219
|
-
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
20220
|
-
generated = false;
|
|
20221
|
-
}
|
|
20222
|
-
if (generated === null)
|
|
20223
|
-
continue;
|
|
20224
|
-
const regex = patternRegex(dir, pattern);
|
|
20225
|
-
if (regex)
|
|
20226
|
-
out.push({ regex, generated });
|
|
20135
|
+
hash.update(line.startsWith("@@ ") ? "@@" : line);
|
|
20136
|
+
hash.update(`
|
|
20137
|
+
`);
|
|
20227
20138
|
}
|
|
20228
|
-
return
|
|
20139
|
+
return `sha256:${hash.digest("hex")}`;
|
|
20229
20140
|
}
|
|
20230
|
-
function
|
|
20231
|
-
const
|
|
20232
|
-
|
|
20233
|
-
return
|
|
20234
|
-
|
|
20235
|
-
rules: rulesOf(dir, text)
|
|
20236
|
-
};
|
|
20237
|
-
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
20238
|
-
const out = new Set;
|
|
20239
|
-
for (const file of paths) {
|
|
20240
|
-
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
20241
|
-
if (last?.generated)
|
|
20242
|
-
out.add(file);
|
|
20243
|
-
}
|
|
20244
|
-
return out;
|
|
20141
|
+
function storeRoot(cwd) {
|
|
20142
|
+
const location = resolveStore(path15.resolve(cwd));
|
|
20143
|
+
if (location instanceof Error)
|
|
20144
|
+
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");
|
|
20145
|
+
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
20245
20146
|
}
|
|
20246
|
-
|
|
20247
|
-
|
|
20248
|
-
|
|
20249
|
-
|
|
20250
|
-
var
|
|
20251
|
-
|
|
20252
|
-
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
20258
|
-
|
|
20259
|
-
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
ext,
|
|
20263
|
-
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
20264
|
-
]),
|
|
20265
|
-
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
20266
|
-
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
20267
|
-
ext,
|
|
20268
|
-
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
20269
|
-
])
|
|
20147
|
+
function ledgerPath(cwd) {
|
|
20148
|
+
const root = storeRoot(cwd);
|
|
20149
|
+
return root.ok ? { ok: true, value: path15.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
20150
|
+
}
|
|
20151
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
20152
|
+
var NETWORK_FS_TYPES = new Set([
|
|
20153
|
+
26985,
|
|
20154
|
+
20859,
|
|
20155
|
+
4266872130,
|
|
20156
|
+
4283649346,
|
|
20157
|
+
1702057286,
|
|
20158
|
+
1397113167,
|
|
20159
|
+
12805120,
|
|
20160
|
+
16914839,
|
|
20161
|
+
1196443219,
|
|
20162
|
+
198183888
|
|
20270
20163
|
]);
|
|
20271
|
-
var
|
|
20272
|
-
var
|
|
20273
|
-
|
|
20274
|
-
|
|
20275
|
-
|
|
20164
|
+
var isNetworkFsType = (type) => NETWORK_FS_TYPES.has(type >>> 0);
|
|
20165
|
+
var onNetworkFs = (dir) => {
|
|
20166
|
+
if (process.platform !== "linux")
|
|
20167
|
+
return false;
|
|
20168
|
+
try {
|
|
20169
|
+
return isNetworkFsType(fs12.statfsSync(dir).type);
|
|
20170
|
+
} catch {
|
|
20171
|
+
return false;
|
|
20172
|
+
}
|
|
20276
20173
|
};
|
|
20277
|
-
var
|
|
20278
|
-
var
|
|
20279
|
-
var
|
|
20280
|
-
var
|
|
20281
|
-
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
"return",
|
|
20286
|
-
"typeof",
|
|
20287
|
-
"instanceof",
|
|
20288
|
-
"case",
|
|
20289
|
-
"do",
|
|
20290
|
-
"else",
|
|
20291
|
-
"in",
|
|
20292
|
-
"of",
|
|
20293
|
-
"new",
|
|
20294
|
-
"delete",
|
|
20295
|
-
"void",
|
|
20296
|
-
"throw",
|
|
20297
|
-
"yield",
|
|
20298
|
-
"await"
|
|
20299
|
-
]);
|
|
20300
|
-
function substanceOf(file, text) {
|
|
20301
|
-
const syntax = syntaxOf(file);
|
|
20302
|
-
if (!syntax)
|
|
20303
|
-
return plainSubstance(text);
|
|
20304
|
-
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
20305
|
-
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
20306
|
-
return plainSubstance(text);
|
|
20307
|
-
return lex(syntax, text);
|
|
20308
|
-
}
|
|
20309
|
-
function plainSubstance(text) {
|
|
20310
|
-
return text.split(`
|
|
20311
|
-
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
20312
|
-
`);
|
|
20313
|
-
}
|
|
20314
|
-
function lex(syntax, text) {
|
|
20315
|
-
const collapse = !syntax.indented;
|
|
20316
|
-
let out = "";
|
|
20317
|
-
let lineStart = 0;
|
|
20318
|
-
let kept = 0;
|
|
20319
|
-
let pendingSpace = false;
|
|
20320
|
-
const frames = [{ kind: "code", braces: 0 }];
|
|
20321
|
-
let index = 0;
|
|
20322
|
-
if (text.startsWith("#!")) {
|
|
20323
|
-
const end = text.indexOf(`
|
|
20324
|
-
`);
|
|
20325
|
-
out = end < 0 ? text : text.slice(0, end);
|
|
20326
|
-
kept = out.length;
|
|
20327
|
-
index = end < 0 ? text.length : end;
|
|
20174
|
+
var LOCK_WAIT_MS = 5000;
|
|
20175
|
+
var LOCK_STALE_MS = 30000;
|
|
20176
|
+
var LOCK_OWNER = "owner";
|
|
20177
|
+
var lockToken = (lock) => {
|
|
20178
|
+
try {
|
|
20179
|
+
return fs12.readFileSync(path15.join(lock, LOCK_OWNER), "utf8");
|
|
20180
|
+
} catch {
|
|
20181
|
+
return null;
|
|
20328
20182
|
}
|
|
20329
|
-
|
|
20330
|
-
|
|
20331
|
-
|
|
20332
|
-
|
|
20333
|
-
|
|
20183
|
+
};
|
|
20184
|
+
var lockAgeMs = (lock) => {
|
|
20185
|
+
for (const target of [path15.join(lock, LOCK_OWNER), lock]) {
|
|
20186
|
+
try {
|
|
20187
|
+
return Date.now() - fs12.statSync(target).mtimeMs;
|
|
20188
|
+
} catch {}
|
|
20189
|
+
}
|
|
20190
|
+
return null;
|
|
20191
|
+
};
|
|
20192
|
+
var ledgerLock = {
|
|
20193
|
+
acquire(lock) {
|
|
20194
|
+
try {
|
|
20195
|
+
fs12.mkdirSync(lock);
|
|
20196
|
+
} catch (error) {
|
|
20197
|
+
if (error.code === "EEXIST")
|
|
20198
|
+
return null;
|
|
20199
|
+
throw error;
|
|
20334
20200
|
}
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
|
|
20338
|
-
|
|
20339
|
-
|
|
20340
|
-
|
|
20341
|
-
|
|
20342
|
-
|
|
20343
|
-
|
|
20344
|
-
|
|
20345
|
-
|
|
20346
|
-
|
|
20347
|
-
out = out.slice(0, end);
|
|
20201
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
20202
|
+
fs12.writeFileSync(path15.join(lock, LOCK_OWNER), token);
|
|
20203
|
+
return token;
|
|
20204
|
+
},
|
|
20205
|
+
release(lock, token) {
|
|
20206
|
+
if (lockToken(lock) !== token)
|
|
20207
|
+
return false;
|
|
20208
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
20209
|
+
try {
|
|
20210
|
+
fs12.renameSync(lock, tomb);
|
|
20211
|
+
} catch {
|
|
20212
|
+
return false;
|
|
20348
20213
|
}
|
|
20349
|
-
|
|
20350
|
-
|
|
20351
|
-
|
|
20352
|
-
|
|
20353
|
-
|
|
20214
|
+
if (lockToken(tomb) !== token) {
|
|
20215
|
+
try {
|
|
20216
|
+
fs12.renameSync(tomb, lock);
|
|
20217
|
+
} catch {}
|
|
20218
|
+
return false;
|
|
20354
20219
|
}
|
|
20355
|
-
|
|
20356
|
-
|
|
20357
|
-
|
|
20358
|
-
|
|
20359
|
-
|
|
20360
|
-
|
|
20220
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
20221
|
+
return true;
|
|
20222
|
+
},
|
|
20223
|
+
token: lockToken,
|
|
20224
|
+
reap(lock, observed) {
|
|
20225
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
20226
|
+
try {
|
|
20227
|
+
fs12.renameSync(lock, tomb);
|
|
20228
|
+
} catch {
|
|
20229
|
+
return false;
|
|
20230
|
+
}
|
|
20231
|
+
if (lockToken(tomb) === observed) {
|
|
20232
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
20361
20233
|
return true;
|
|
20362
|
-
|
|
20363
|
-
|
|
20364
|
-
|
|
20365
|
-
|
|
20366
|
-
|
|
20367
|
-
const scanString = (quote, escapes, multiline) => {
|
|
20368
|
-
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
20369
|
-
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
20370
|
-
let end = index + quote.length;
|
|
20371
|
-
while (end < text.length) {
|
|
20372
|
-
const char = text[end];
|
|
20373
|
-
if (escapes && char === "\\") {
|
|
20374
|
-
end += 2;
|
|
20375
|
-
continue;
|
|
20376
|
-
}
|
|
20377
|
-
if (text.startsWith(quote, end)) {
|
|
20378
|
-
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
20379
|
-
return false;
|
|
20380
|
-
emitVerbatim(text.slice(index, end + quote.length));
|
|
20381
|
-
index = end + quote.length;
|
|
20382
|
-
return true;
|
|
20383
|
-
}
|
|
20384
|
-
if (char === `
|
|
20385
|
-
` && !multiline)
|
|
20386
|
-
return false;
|
|
20387
|
-
end += 1;
|
|
20234
|
+
}
|
|
20235
|
+
try {
|
|
20236
|
+
fs12.renameSync(tomb, lock);
|
|
20237
|
+
} catch {
|
|
20238
|
+
fs12.rmSync(tomb, { recursive: true, force: true });
|
|
20388
20239
|
}
|
|
20389
20240
|
return false;
|
|
20390
|
-
}
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
|
|
20394
|
-
|
|
20395
|
-
|
|
20396
|
-
|
|
20397
|
-
|
|
20398
|
-
|
|
20399
|
-
|
|
20400
|
-
|
|
20401
|
-
|
|
20402
|
-
|
|
20403
|
-
emitVerbatim("${");
|
|
20404
|
-
frames.push({ kind: "code", braces: 0 });
|
|
20405
|
-
index += 2;
|
|
20406
|
-
} else {
|
|
20407
|
-
out += char;
|
|
20408
|
-
kept = out.length;
|
|
20409
|
-
index += 1;
|
|
20410
|
-
}
|
|
20411
|
-
continue;
|
|
20412
|
-
}
|
|
20413
|
-
if (char === `
|
|
20414
|
-
`) {
|
|
20415
|
-
newline();
|
|
20416
|
-
index += 1;
|
|
20417
|
-
continue;
|
|
20418
|
-
}
|
|
20419
|
-
if (SPACE.test(char)) {
|
|
20420
|
-
if (collapse)
|
|
20421
|
-
pendingSpace = true;
|
|
20422
|
-
else
|
|
20423
|
-
out += char;
|
|
20424
|
-
index += 1;
|
|
20425
|
-
continue;
|
|
20426
|
-
}
|
|
20427
|
-
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
20428
|
-
if (lineOpener) {
|
|
20429
|
-
const end = text.indexOf(`
|
|
20430
|
-
`, index);
|
|
20431
|
-
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
20432
|
-
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
20433
|
-
emitVerbatim(comment);
|
|
20434
|
-
else
|
|
20435
|
-
pendingSpace = true;
|
|
20436
|
-
index = end < 0 ? text.length : end;
|
|
20437
|
-
continue;
|
|
20438
|
-
}
|
|
20439
|
-
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
20440
|
-
if (block) {
|
|
20441
|
-
const end = text.indexOf(block[1], index + block[0].length);
|
|
20442
|
-
if (end < 0)
|
|
20443
|
-
return null;
|
|
20444
|
-
const comment = text.slice(index, end + block[1].length);
|
|
20445
|
-
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
20446
|
-
emitVerbatim(comment);
|
|
20447
|
-
else
|
|
20448
|
-
pendingSpace = true;
|
|
20449
|
-
if (comment.includes(`
|
|
20450
|
-
`))
|
|
20451
|
-
newline();
|
|
20452
|
-
index = end + block[1].length;
|
|
20453
|
-
continue;
|
|
20454
|
-
}
|
|
20455
|
-
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
20456
|
-
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
20457
|
-
return null;
|
|
20458
|
-
continue;
|
|
20459
|
-
}
|
|
20460
|
-
if (syntax.quotes.includes(char)) {
|
|
20461
|
-
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
20462
|
-
return null;
|
|
20463
|
-
continue;
|
|
20464
|
-
}
|
|
20465
|
-
if (syntax.literal?.includes(char)) {
|
|
20466
|
-
if (!scanString(char, false, false))
|
|
20467
|
-
return null;
|
|
20468
|
-
continue;
|
|
20469
|
-
}
|
|
20470
|
-
if (syntax.rawBacktick && char === "`") {
|
|
20471
|
-
if (!scanString("`", false, true))
|
|
20472
|
-
return null;
|
|
20473
|
-
continue;
|
|
20474
|
-
}
|
|
20475
|
-
if (syntax.js && char === "`") {
|
|
20476
|
-
emitVerbatim(char);
|
|
20477
|
-
frames.push({ kind: "template" });
|
|
20478
|
-
index += 1;
|
|
20241
|
+
}
|
|
20242
|
+
};
|
|
20243
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
20244
|
+
const lock = `${file}.lock`;
|
|
20245
|
+
const deadline = Date.now() + waitMs;
|
|
20246
|
+
let token = null;
|
|
20247
|
+
for (;; ) {
|
|
20248
|
+
token = ledgerLock.acquire(lock);
|
|
20249
|
+
if (token)
|
|
20250
|
+
break;
|
|
20251
|
+
const observed = lockToken(lock);
|
|
20252
|
+
const age = lockAgeMs(lock);
|
|
20253
|
+
if (age !== null && age > LOCK_STALE_MS && ledgerLock.reap(lock, observed))
|
|
20479
20254
|
continue;
|
|
20480
|
-
|
|
20481
|
-
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
20255
|
+
if (Date.now() >= deadline)
|
|
20482
20256
|
return null;
|
|
20483
|
-
|
|
20484
|
-
|
|
20485
|
-
|
|
20486
|
-
|
|
20257
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
20258
|
+
}
|
|
20259
|
+
try {
|
|
20260
|
+
return fn();
|
|
20261
|
+
} finally {
|
|
20262
|
+
ledgerLock.release(lock, token);
|
|
20263
|
+
}
|
|
20264
|
+
}
|
|
20265
|
+
var sha2562 = (text) => createHash9("sha256").update(text).digest("hex");
|
|
20266
|
+
var chainHash = (prevHash, body) => sha2562(`${prevHash ?? ""}
|
|
20267
|
+
${body}`);
|
|
20268
|
+
var TAIL_BYTES = 64 * 1024;
|
|
20269
|
+
var lastLine = (fd, size) => {
|
|
20270
|
+
for (let span = Math.min(size, TAIL_BYTES);span > 0; span = Math.min(size, span * 2)) {
|
|
20271
|
+
const buffer = Buffer.alloc(span);
|
|
20272
|
+
fs12.readSync(fd, buffer, 0, span, size - span);
|
|
20273
|
+
const end = buffer.lastIndexOf(10);
|
|
20274
|
+
if (end < 0) {
|
|
20275
|
+
if (span === size)
|
|
20487
20276
|
return null;
|
|
20277
|
+
continue;
|
|
20488
20278
|
}
|
|
20489
|
-
|
|
20490
|
-
|
|
20491
|
-
|
|
20492
|
-
|
|
20493
|
-
|
|
20494
|
-
|
|
20279
|
+
let stop = end;
|
|
20280
|
+
let text = "";
|
|
20281
|
+
while (stop >= 0) {
|
|
20282
|
+
const start = buffer.lastIndexOf(10, stop - 1);
|
|
20283
|
+
text = buffer.toString("utf8", start + 1, stop).trim();
|
|
20284
|
+
if (text) {
|
|
20285
|
+
if (start >= 0 || span === size)
|
|
20286
|
+
return text;
|
|
20287
|
+
break;
|
|
20288
|
+
}
|
|
20289
|
+
if (start < 0) {
|
|
20290
|
+
if (span === size)
|
|
20495
20291
|
return null;
|
|
20496
|
-
|
|
20497
|
-
end += 1;
|
|
20498
|
-
else if (text[end] === "[")
|
|
20499
|
-
inClass = true;
|
|
20500
|
-
else if (text[end] === "]")
|
|
20501
|
-
inClass = false;
|
|
20502
|
-
end += 1;
|
|
20292
|
+
break;
|
|
20503
20293
|
}
|
|
20504
|
-
|
|
20505
|
-
return null;
|
|
20506
|
-
emitVerbatim(text.slice(index, end + 1));
|
|
20507
|
-
index = end + 1;
|
|
20508
|
-
continue;
|
|
20294
|
+
stop = start;
|
|
20509
20295
|
}
|
|
20510
|
-
if (
|
|
20511
|
-
|
|
20512
|
-
|
|
20513
|
-
|
|
20514
|
-
|
|
20515
|
-
|
|
20516
|
-
|
|
20517
|
-
|
|
20518
|
-
|
|
20519
|
-
|
|
20520
|
-
|
|
20296
|
+
if (span === size)
|
|
20297
|
+
return text || null;
|
|
20298
|
+
}
|
|
20299
|
+
return null;
|
|
20300
|
+
};
|
|
20301
|
+
var tighten = (fd) => {
|
|
20302
|
+
try {
|
|
20303
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
20304
|
+
fs12.fchmodSync(fd, 384);
|
|
20305
|
+
} catch {}
|
|
20306
|
+
};
|
|
20307
|
+
function appendLine(file, build, options) {
|
|
20308
|
+
const write = () => {
|
|
20309
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
20310
|
+
try {
|
|
20311
|
+
if (process.platform !== "win32")
|
|
20312
|
+
tighten(fd);
|
|
20313
|
+
const size = fs12.fstatSync(fd).size;
|
|
20314
|
+
let fence = "";
|
|
20315
|
+
if (size > 0) {
|
|
20316
|
+
const last = Buffer.alloc(1);
|
|
20317
|
+
fs12.readSync(fd, last, 0, 1, size - 1);
|
|
20318
|
+
if (last[0] !== 10)
|
|
20319
|
+
fence = `
|
|
20320
|
+
`;
|
|
20521
20321
|
}
|
|
20322
|
+
const line = build(lastLine(fd, size));
|
|
20323
|
+
if (!line.ok)
|
|
20324
|
+
return line;
|
|
20325
|
+
const bytes = Buffer.from(fence + line.value);
|
|
20326
|
+
const written = fs12.writeSync(fd, bytes);
|
|
20327
|
+
if (written !== bytes.length)
|
|
20328
|
+
return err("unavailable", `short write to ${file}: ${written} of ${bytes.length} bytes; the row is torn and will be skipped`);
|
|
20329
|
+
return { ok: true, value: undefined };
|
|
20330
|
+
} finally {
|
|
20331
|
+
fs12.closeSync(fd);
|
|
20522
20332
|
}
|
|
20523
|
-
|
|
20524
|
-
|
|
20333
|
+
};
|
|
20334
|
+
try {
|
|
20335
|
+
fs12.mkdirSync(path15.dirname(file), { recursive: true });
|
|
20336
|
+
if (!options.forceLock && !onNetworkFs(path15.dirname(file)))
|
|
20337
|
+
return write();
|
|
20338
|
+
const locked = withLedgerLock(file, write);
|
|
20339
|
+
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
20340
|
+
} catch (error) {
|
|
20341
|
+
return err("unavailable", `cannot append to ${file}: ${error.message}`);
|
|
20525
20342
|
}
|
|
20526
|
-
if (frames.length !== 1)
|
|
20527
|
-
return null;
|
|
20528
|
-
newline();
|
|
20529
|
-
return out;
|
|
20530
20343
|
}
|
|
20531
|
-
|
|
20532
|
-
|
|
20533
|
-
|
|
20534
|
-
|
|
20535
|
-
|
|
20536
|
-
|
|
20537
|
-
|
|
20538
|
-
|
|
20539
|
-
|
|
20344
|
+
var sealed = (row, id, at, previous) => {
|
|
20345
|
+
const { prevHash: _prev, rowHash: _row, ...fields } = row;
|
|
20346
|
+
const prevHash = previous === null ? null : sha2562(previous);
|
|
20347
|
+
const body = { v: LEDGER_VERSION, id, at, ...fields, prevHash };
|
|
20348
|
+
const full = { ...body, rowHash: chainHash(prevHash, JSON.stringify(body)) };
|
|
20349
|
+
return { full, line: `${JSON.stringify(full)}
|
|
20350
|
+
` };
|
|
20351
|
+
};
|
|
20352
|
+
function appendRaw(cwd, row, options) {
|
|
20353
|
+
const file = ledgerPath(cwd);
|
|
20354
|
+
if (!file.ok)
|
|
20355
|
+
return file;
|
|
20356
|
+
const id = newId2();
|
|
20357
|
+
const at = (options.now ?? new Date).toISOString();
|
|
20358
|
+
let full = null;
|
|
20359
|
+
const written = appendLine(file.value, (previous) => {
|
|
20360
|
+
const next = sealed(row, id, at, previous);
|
|
20361
|
+
const bytes = Buffer.byteLength(next.line);
|
|
20362
|
+
if (bytes > MAX_LINE_BYTES)
|
|
20363
|
+
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>`);
|
|
20364
|
+
full = next.full;
|
|
20365
|
+
return { ok: true, value: next.line };
|
|
20366
|
+
}, options);
|
|
20367
|
+
return written.ok ? { ok: true, value: full } : written;
|
|
20540
20368
|
}
|
|
20541
|
-
function
|
|
20542
|
-
|
|
20369
|
+
function observedRowBytes(row) {
|
|
20370
|
+
const sample = sealed({ ...row, observer: "workit_cli" }, newId2(), new Date().toISOString(), "");
|
|
20371
|
+
return Buffer.byteLength(sample.line);
|
|
20543
20372
|
}
|
|
20544
|
-
|
|
20545
|
-
|
|
20546
|
-
if (bytes.includes(0))
|
|
20547
|
-
return null;
|
|
20548
|
-
try {
|
|
20549
|
-
return decoder.decode(bytes);
|
|
20550
|
-
} catch {
|
|
20551
|
-
return null;
|
|
20552
|
-
}
|
|
20553
|
-
}
|
|
20554
|
-
function cosmeticChange(file, before, after) {
|
|
20555
|
-
const left = textOf(before);
|
|
20556
|
-
const right = textOf(after);
|
|
20557
|
-
if (left === null || right === null)
|
|
20558
|
-
return false;
|
|
20559
|
-
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
20560
|
-
return false;
|
|
20561
|
-
const a = substanceOf(file, left);
|
|
20562
|
-
return a !== null && a === substanceOf(file, right);
|
|
20373
|
+
function appendObserved(cwd, row, options = {}) {
|
|
20374
|
+
return appendRaw(cwd, { ...row, observer: "workit_cli" }, options);
|
|
20563
20375
|
}
|
|
20564
|
-
|
|
20565
|
-
|
|
20566
|
-
var
|
|
20567
|
-
|
|
20568
|
-
|
|
20569
|
-
|
|
20376
|
+
var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
20377
|
+
var str2 = (value) => typeof value === "string" ? value : null;
|
|
20378
|
+
var normalizeActor = (value) => {
|
|
20379
|
+
const actor = isRecord3(value) ? value : {};
|
|
20380
|
+
return {
|
|
20381
|
+
host: str2(actor.host) ?? "unknown",
|
|
20382
|
+
session: str2(actor.session),
|
|
20383
|
+
agentId: str2(actor.agentId)
|
|
20384
|
+
};
|
|
20570
20385
|
};
|
|
20571
|
-
|
|
20572
|
-
|
|
20573
|
-
|
|
20574
|
-
|
|
20575
|
-
|
|
20576
|
-
|
|
20577
|
-
|
|
20578
|
-
"
|
|
20579
|
-
|
|
20580
|
-
"
|
|
20581
|
-
|
|
20582
|
-
|
|
20583
|
-
"gates",
|
|
20584
|
-
"policy",
|
|
20585
|
-
"policies",
|
|
20586
|
-
"hook",
|
|
20587
|
-
"hooks",
|
|
20588
|
-
"husky",
|
|
20589
|
-
"lefthook",
|
|
20590
|
-
"release",
|
|
20591
|
-
"releases",
|
|
20592
|
-
"migration",
|
|
20593
|
-
"migrations",
|
|
20594
|
-
"migrate",
|
|
20595
|
-
"workflows"
|
|
20596
|
-
]);
|
|
20597
|
-
var RISK_FILES = new Set([
|
|
20598
|
-
".gitlab-ci.yml",
|
|
20599
|
-
"Jenkinsfile",
|
|
20600
|
-
"azure-pipelines.yml",
|
|
20601
|
-
".pre-commit-config.yaml",
|
|
20602
|
-
".workit/risk-paths"
|
|
20603
|
-
]);
|
|
20604
|
-
var RISK_NAMES = new Set([".gitattributes"]);
|
|
20605
|
-
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
20606
|
-
var LOCKFILES = new Set([
|
|
20607
|
-
"bun.lock",
|
|
20608
|
-
"bun.lockb",
|
|
20609
|
-
"package-lock.json",
|
|
20610
|
-
"npm-shrinkwrap.json",
|
|
20611
|
-
"yarn.lock",
|
|
20612
|
-
"pnpm-lock.yaml",
|
|
20613
|
-
"Cargo.lock",
|
|
20614
|
-
"go.sum",
|
|
20615
|
-
"poetry.lock",
|
|
20616
|
-
"uv.lock",
|
|
20617
|
-
"Pipfile.lock",
|
|
20618
|
-
"Gemfile.lock",
|
|
20619
|
-
"composer.lock"
|
|
20620
|
-
]);
|
|
20621
|
-
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc|txt)$/iu;
|
|
20622
|
-
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
20623
|
-
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
20624
|
-
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__|spec|specs|fixtures)\/|\.(?:test|spec)\.[^/]+$/iu;
|
|
20625
|
-
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
20626
|
-
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
20627
|
-
function kindOf(file) {
|
|
20628
|
-
if (file.binary)
|
|
20629
|
-
return "behavior";
|
|
20630
|
-
if (file.generated)
|
|
20631
|
-
return "generated";
|
|
20632
|
-
if (LOCKFILES.has(basename(file.path)))
|
|
20633
|
-
return "lockfile";
|
|
20634
|
-
if (isDoc(file.path))
|
|
20635
|
-
return "doc";
|
|
20636
|
-
if (file.cosmetic)
|
|
20637
|
-
return "cosmetic";
|
|
20638
|
-
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
20386
|
+
function supersedeAllowed(by, target, onBranch = (row) => (by.branch ?? null) === (row.branch ?? null)) {
|
|
20387
|
+
if (by.type !== target.type)
|
|
20388
|
+
return `a ${by.type} row cannot supersede a ${target.type} row`;
|
|
20389
|
+
if (!by.actor.session || by.actor.session !== target.actor.session)
|
|
20390
|
+
return "only the session that wrote a row can supersede it";
|
|
20391
|
+
if (!onBranch(target))
|
|
20392
|
+
return `the row is on another branch (${String(target.branch ?? "none")}); a row supersedes only one on its own branch`;
|
|
20393
|
+
if (by.type === "verdict" && (by.kind ?? "review") !== (target.kind ?? "review"))
|
|
20394
|
+
return `a ${String(by.kind ?? "review")} verdict cannot supersede a ${String(target.kind ?? "review")} verdict; record it with --kind ${String(target.kind ?? "review")}`;
|
|
20395
|
+
if (by.type === "verdict" && by.self === true && target.self !== true)
|
|
20396
|
+
return "a self verdict cannot replace an independent verdict";
|
|
20397
|
+
return null;
|
|
20639
20398
|
}
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20399
|
+
function readLedger(cwd, options = {}) {
|
|
20400
|
+
const file = ledgerPath(cwd);
|
|
20401
|
+
if (!file.ok)
|
|
20402
|
+
return file;
|
|
20403
|
+
let text = "";
|
|
20404
|
+
let stat = null;
|
|
20405
|
+
try {
|
|
20406
|
+
stat = fs12.statSync(file.value);
|
|
20407
|
+
text = fs12.readFileSync(file.value, "utf8");
|
|
20408
|
+
} catch (error) {
|
|
20409
|
+
if (error.code === "ENOENT")
|
|
20410
|
+
return {
|
|
20411
|
+
ok: true,
|
|
20412
|
+
value: {
|
|
20413
|
+
path: file.value,
|
|
20414
|
+
rows: [],
|
|
20415
|
+
skipped: 0,
|
|
20416
|
+
integrity: options.integrity ? { chained: 0, legacy: 0, unverified: [] } : null
|
|
20417
|
+
}
|
|
20418
|
+
};
|
|
20419
|
+
return err("unavailable", `cannot read ${file.value}: ${error.message}`);
|
|
20420
|
+
}
|
|
20421
|
+
const rows = [];
|
|
20422
|
+
let skipped = 0;
|
|
20423
|
+
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
20424
|
+
const earlier = new Set([null]);
|
|
20425
|
+
let chainStarted = false;
|
|
20426
|
+
const ids = new Set;
|
|
20427
|
+
for (const raw of text.split(`
|
|
20428
|
+
`)) {
|
|
20429
|
+
const trimmed = raw.trim();
|
|
20430
|
+
if (!trimmed)
|
|
20431
|
+
continue;
|
|
20432
|
+
const linked = (prevHash) => earlier.has(prevHash);
|
|
20433
|
+
if (options.integrity)
|
|
20434
|
+
earlier.add(sha2562(trimmed));
|
|
20435
|
+
let parsed;
|
|
20436
|
+
try {
|
|
20437
|
+
parsed = JSON.parse(trimmed);
|
|
20438
|
+
} catch {
|
|
20439
|
+
skipped += 1;
|
|
20440
|
+
continue;
|
|
20656
20441
|
}
|
|
20657
|
-
|
|
20658
|
-
|
|
20442
|
+
if (!isRecord3(parsed) || !str2(parsed.id) || !str2(parsed.type) || !str2(parsed.at)) {
|
|
20443
|
+
skipped += 1;
|
|
20444
|
+
continue;
|
|
20445
|
+
}
|
|
20446
|
+
let rowIntegrity = options.integrity ? "verified" : undefined;
|
|
20447
|
+
let reason = null;
|
|
20448
|
+
if (!options.integrity) {} else if (typeof parsed.rowHash === "string") {
|
|
20449
|
+
chainStarted = true;
|
|
20450
|
+
const { rowHash, ...body } = parsed;
|
|
20451
|
+
const prevHash = str2(parsed.prevHash);
|
|
20452
|
+
if (chainHash(prevHash, JSON.stringify(body)) !== rowHash)
|
|
20453
|
+
reason = "hash_mismatch";
|
|
20454
|
+
else if (!linked(prevHash))
|
|
20455
|
+
reason = "broken_link";
|
|
20456
|
+
} else if (chainStarted)
|
|
20457
|
+
reason = "unsigned";
|
|
20458
|
+
else
|
|
20459
|
+
rowIntegrity = "legacy";
|
|
20460
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
20461
|
+
reason = "duplicate_id";
|
|
20462
|
+
if (options.integrity)
|
|
20463
|
+
ids.add(parsed.id);
|
|
20464
|
+
if (reason)
|
|
20465
|
+
rowIntegrity = "unverified";
|
|
20466
|
+
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
20467
|
+
const row = {
|
|
20468
|
+
...parsed,
|
|
20469
|
+
v: typeof parsed.v === "number" ? parsed.v : LEDGER_VERSION,
|
|
20470
|
+
id: parsed.id,
|
|
20471
|
+
at: parsed.at,
|
|
20472
|
+
type: parsed.type,
|
|
20473
|
+
actor: normalizeActor(parsed.actor),
|
|
20474
|
+
branch: str2(parsed.branch),
|
|
20475
|
+
head: str2(parsed.head),
|
|
20476
|
+
base: str2(parsed.base),
|
|
20477
|
+
baseSha: str2(parsed.baseSha),
|
|
20478
|
+
patchId: str2(parsed.patchId),
|
|
20479
|
+
diffHash: str2(parsed.diffHash),
|
|
20480
|
+
tree: str2(parsed.tree),
|
|
20481
|
+
dirty: typeof parsed.dirty === "boolean" ? parsed.dirty : null,
|
|
20482
|
+
...pr === undefined ? {} : { pr },
|
|
20483
|
+
...str2(parsed.supersedes) ? { supersedes: parsed.supersedes } : {},
|
|
20484
|
+
seq: rows.length + 1,
|
|
20485
|
+
superseded: false,
|
|
20486
|
+
supersedeIgnored: false,
|
|
20487
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
20488
|
+
...reason ? { integrityReason: reason } : {}
|
|
20489
|
+
};
|
|
20490
|
+
rows.push(row);
|
|
20491
|
+
if (!options.integrity)
|
|
20492
|
+
continue;
|
|
20493
|
+
if (rowIntegrity === "verified")
|
|
20494
|
+
integrity.chained += 1;
|
|
20495
|
+
else if (rowIntegrity === "legacy")
|
|
20496
|
+
integrity.legacy += 1;
|
|
20497
|
+
else if (reason)
|
|
20498
|
+
integrity.unverified.push({
|
|
20499
|
+
seq: row.seq,
|
|
20500
|
+
id: row.id,
|
|
20501
|
+
type: row.type,
|
|
20502
|
+
branch: row.branch,
|
|
20503
|
+
reason
|
|
20504
|
+
});
|
|
20659
20505
|
}
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
|
|
20677
|
-
|
|
20678
|
-
|
|
20679
|
-
|
|
20680
|
-
|
|
20681
|
-
|
|
20682
|
-
|
|
20683
|
-
|
|
20684
|
-
|
|
20685
|
-
|
|
20686
|
-
|
|
20687
|
-
files: behavior.length,
|
|
20688
|
-
riskFiles
|
|
20689
|
-
});
|
|
20690
|
-
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
20691
|
-
if (input.riskTier === "high")
|
|
20692
|
-
return result("full", ["the task judgment records high risk"]);
|
|
20693
|
-
if (riskFiles.length)
|
|
20694
|
-
return result("full", [
|
|
20695
|
-
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
20696
|
-
]);
|
|
20697
|
-
if (!behavior.length)
|
|
20698
|
-
return result("none", [
|
|
20699
|
-
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
20700
|
-
]);
|
|
20701
|
-
if (lines >= fullMinLines)
|
|
20702
|
-
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
20703
|
-
if (lines > selfMaxLines)
|
|
20704
|
-
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
20705
|
-
if (behavior.length > selfMaxFiles)
|
|
20706
|
-
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
20707
|
-
if (opaque.length)
|
|
20708
|
-
return result("focused", [
|
|
20709
|
-
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
20710
|
-
]);
|
|
20711
|
-
return result("self", [
|
|
20712
|
-
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
20713
|
-
]);
|
|
20506
|
+
const scopes = new Map;
|
|
20507
|
+
const scopeOf = (branch) => {
|
|
20508
|
+
let scope = scopes.get(branch);
|
|
20509
|
+
if (!scope)
|
|
20510
|
+
scopes.set(branch, scope = branchScope(cwd, branch));
|
|
20511
|
+
return scope;
|
|
20512
|
+
};
|
|
20513
|
+
const byId = new Map;
|
|
20514
|
+
for (const row of rows) {
|
|
20515
|
+
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
20516
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
20517
|
+
target.superseded = true;
|
|
20518
|
+
else if (row.supersedes)
|
|
20519
|
+
row.supersedeIgnored = true;
|
|
20520
|
+
if (!byId.has(row.id))
|
|
20521
|
+
byId.set(row.id, row);
|
|
20522
|
+
}
|
|
20523
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
20524
|
+
tamperCache.set(file.value, {
|
|
20525
|
+
size: stat.size,
|
|
20526
|
+
stamp: fileStamp(stat),
|
|
20527
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
20528
|
+
});
|
|
20529
|
+
return {
|
|
20530
|
+
ok: true,
|
|
20531
|
+
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
20532
|
+
};
|
|
20714
20533
|
}
|
|
20715
|
-
|
|
20534
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
20535
|
+
var tamperCache = new Map;
|
|
20536
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
20537
|
+
function ledgerTampering(cwd) {
|
|
20538
|
+
const file = ledgerPath(cwd);
|
|
20539
|
+
if (!file.ok)
|
|
20540
|
+
return [];
|
|
20716
20541
|
try {
|
|
20717
|
-
const
|
|
20718
|
-
|
|
20719
|
-
|
|
20720
|
-
|
|
20721
|
-
if (hasOpaqueLineBreak(text))
|
|
20722
|
-
return { path: file, added: 0, deleted: 0, binary: true };
|
|
20723
|
-
const lines = text.split(`
|
|
20724
|
-
`).length - (text.endsWith(`
|
|
20725
|
-
`) ? 1 : 0);
|
|
20726
|
-
return { path: file, added: lines, deleted: 0 };
|
|
20542
|
+
const stat = fs12.statSync(file.value);
|
|
20543
|
+
const cached = tamperCache.get(file.value);
|
|
20544
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
20545
|
+
return cached.breaks;
|
|
20727
20546
|
} catch {
|
|
20728
|
-
return
|
|
20547
|
+
return [];
|
|
20729
20548
|
}
|
|
20549
|
+
const read = readLedger(cwd, { integrity: true });
|
|
20550
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
20730
20551
|
}
|
|
20731
|
-
|
|
20732
|
-
|
|
20733
|
-
|
|
20734
|
-
|
|
20735
|
-
|
|
20736
|
-
|
|
20737
|
-
|
|
20738
|
-
|
|
20739
|
-
|
|
20740
|
-
|
|
20741
|
-
|
|
20742
|
-
|
|
20743
|
-
|
|
20744
|
-
|
|
20745
|
-
|
|
20746
|
-
|
|
20747
|
-
|
|
20748
|
-
|
|
20749
|
-
|
|
20750
|
-
|
|
20751
|
-
|
|
20752
|
-
|
|
20753
|
-
|
|
20754
|
-
|
|
20755
|
-
|
|
20552
|
+
function tamperedUnblock(cwd) {
|
|
20553
|
+
const file = ledgerPath(cwd);
|
|
20554
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
20555
|
+
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`;
|
|
20556
|
+
}
|
|
20557
|
+
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)}`;
|
|
20558
|
+
function verdictBasis(verdict, current) {
|
|
20559
|
+
if (verdict.head && verdict.head === current.head)
|
|
20560
|
+
return "fresh";
|
|
20561
|
+
if (verdict.patchId && verdict.patchId === current.patchId && verdict.diffHash && verdict.diffHash === current.diffHash)
|
|
20562
|
+
return "carried";
|
|
20563
|
+
return "stale";
|
|
20564
|
+
}
|
|
20565
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
20566
|
+
function trailerSessions(cwd, base, head) {
|
|
20567
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
20568
|
+
return [];
|
|
20569
|
+
const out = git4(cwd, [
|
|
20570
|
+
"log",
|
|
20571
|
+
"--format=%(trailers:key=Workit-Session,valueonly,separator=%x00)",
|
|
20572
|
+
`${base}..${head}`
|
|
20573
|
+
]);
|
|
20574
|
+
return (out ?? "").split(/[\0\n]/u).map((value) => value.trim()).filter(Boolean);
|
|
20575
|
+
}
|
|
20576
|
+
var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
20577
|
+
function branchScope(cwd, branch) {
|
|
20578
|
+
const renamedAt = new Map;
|
|
20579
|
+
let since = Number.NEGATIVE_INFINITY;
|
|
20580
|
+
if (safeRef2(branch)) {
|
|
20581
|
+
const log = git4(cwd, [
|
|
20582
|
+
"log",
|
|
20583
|
+
"-g",
|
|
20584
|
+
"--date=unix",
|
|
20585
|
+
"--format=%gd%x09%gs",
|
|
20586
|
+
`refs/heads/${branch}`,
|
|
20587
|
+
"--"
|
|
20588
|
+
]);
|
|
20589
|
+
for (const line of (log ?? "").split(`
|
|
20590
|
+
`)) {
|
|
20591
|
+
const [selector = "", subject = ""] = line.split("\t");
|
|
20592
|
+
const seconds = Number(/@\{(\d+)\}$/u.exec(selector)?.[1]);
|
|
20593
|
+
if (!Number.isFinite(seconds))
|
|
20594
|
+
continue;
|
|
20595
|
+
since = seconds * 1000;
|
|
20596
|
+
const match = RENAMED.exec(subject);
|
|
20597
|
+
if (!match || match[1] === branch)
|
|
20598
|
+
continue;
|
|
20599
|
+
const until = (seconds + 1) * 1000;
|
|
20600
|
+
renamedAt.set(match[1], Math.max(renamedAt.get(match[1]) ?? 0, until));
|
|
20601
|
+
}
|
|
20756
20602
|
}
|
|
20757
|
-
return
|
|
20603
|
+
return (row) => {
|
|
20604
|
+
if (row.branch === branch)
|
|
20605
|
+
return true;
|
|
20606
|
+
const until = row.branch === null ? undefined : renamedAt.get(row.branch);
|
|
20607
|
+
if (until === undefined)
|
|
20608
|
+
return false;
|
|
20609
|
+
const at = Date.parse(row.at);
|
|
20610
|
+
return at >= since && at <= until;
|
|
20611
|
+
};
|
|
20758
20612
|
}
|
|
20759
|
-
|
|
20760
|
-
|
|
20761
|
-
|
|
20762
|
-
|
|
20763
|
-
|
|
20764
|
-
|
|
20765
|
-
|
|
20766
|
-
|
|
20767
|
-
|
|
20768
|
-
|
|
20613
|
+
var renameAwareBranch = (scopeOf, branch) => {
|
|
20614
|
+
return (target) => {
|
|
20615
|
+
const own = typeof branch === "string" ? branch : null;
|
|
20616
|
+
const theirs = typeof target.branch === "string" ? target.branch : null;
|
|
20617
|
+
if (own === theirs)
|
|
20618
|
+
return true;
|
|
20619
|
+
if (own === null || theirs === null || typeof target.at !== "string")
|
|
20620
|
+
return false;
|
|
20621
|
+
return scopeOf(own)({ branch: theirs, at: target.at });
|
|
20622
|
+
};
|
|
20623
|
+
};
|
|
20624
|
+
function authorSessions(cwd, rows, branch, range = { base: null, head: null }, scope = branchScope(cwd, branch)) {
|
|
20625
|
+
const sessions = new Set;
|
|
20626
|
+
for (const row of rows) {
|
|
20627
|
+
if (!scope(row) || row.type !== "commit.recorded" && row.type !== "task.opened")
|
|
20628
|
+
continue;
|
|
20629
|
+
const session = str2(row.session) ?? row.actor.session;
|
|
20630
|
+
if (session)
|
|
20631
|
+
sessions.add(session);
|
|
20769
20632
|
}
|
|
20770
|
-
|
|
20633
|
+
for (const session of trailerSessions(cwd, range.base, range.head))
|
|
20634
|
+
sessions.add(session);
|
|
20635
|
+
return sessions;
|
|
20771
20636
|
}
|
|
20772
|
-
var
|
|
20773
|
-
|
|
20774
|
-
|
|
20775
|
-
|
|
20776
|
-
|
|
20637
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
20638
|
+
var authoredBy = (authors, session) => {
|
|
20639
|
+
if (authors.has(session))
|
|
20640
|
+
return true;
|
|
20641
|
+
for (const author of authors)
|
|
20642
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
20643
|
+
return true;
|
|
20644
|
+
return false;
|
|
20645
|
+
};
|
|
20646
|
+
var TRUST_REASONS = new Set([
|
|
20647
|
+
"unsigned",
|
|
20648
|
+
"hash_mismatch",
|
|
20649
|
+
"broken_link",
|
|
20650
|
+
"duplicate_id",
|
|
20651
|
+
"unissued_session",
|
|
20652
|
+
"unbound_session"
|
|
20653
|
+
]);
|
|
20654
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
20655
|
+
"cli",
|
|
20656
|
+
"opencode",
|
|
20657
|
+
"pi",
|
|
20658
|
+
"codex_cli",
|
|
20659
|
+
"codex_desktop"
|
|
20660
|
+
]);
|
|
20661
|
+
function sessionTrust(cwd, row) {
|
|
20662
|
+
const session = row.actor.session;
|
|
20663
|
+
if (!session || row.self === true)
|
|
20777
20664
|
return null;
|
|
20778
|
-
const
|
|
20779
|
-
|
|
20780
|
-
|
|
20781
|
-
|
|
20782
|
-
|
|
20783
|
-
|
|
20784
|
-
|
|
20785
|
-
|
|
20786
|
-
|
|
20787
|
-
|
|
20788
|
-
|
|
20789
|
-
|
|
20790
|
-
|
|
20665
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
20666
|
+
const host = row.actor.host;
|
|
20667
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
20668
|
+
if (!issuedOnly)
|
|
20669
|
+
return null;
|
|
20670
|
+
const binding = issuedBinding(cwd, session);
|
|
20671
|
+
if (binding === null)
|
|
20672
|
+
return "unissued_session";
|
|
20673
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
20674
|
+
}
|
|
20675
|
+
function trustReasons(cwd, row) {
|
|
20676
|
+
if (row.integrity === "unverified")
|
|
20677
|
+
return [row.integrityReason ?? "unsigned"];
|
|
20678
|
+
if (row.integrity !== "verified")
|
|
20679
|
+
return ["unsigned"];
|
|
20680
|
+
const session = sessionTrust(cwd, row);
|
|
20681
|
+
return session ? [session] : [];
|
|
20682
|
+
}
|
|
20683
|
+
function withIntegrity(cwd, rows) {
|
|
20684
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
20685
|
+
return rows;
|
|
20686
|
+
const read = readLedger(cwd, { integrity: true });
|
|
20687
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
20688
|
+
return rows.map((row) => {
|
|
20689
|
+
if (row.integrity !== undefined)
|
|
20690
|
+
return row;
|
|
20691
|
+
const fresh = bySeq.get(row.seq);
|
|
20692
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
20693
|
+
});
|
|
20694
|
+
}
|
|
20695
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
20696
|
+
var independenceReasons = (row, authors) => {
|
|
20697
|
+
const reasons = [];
|
|
20698
|
+
if (!row.actor.session)
|
|
20699
|
+
reasons.push("no_session");
|
|
20700
|
+
else if (authoredBy(authors, row.actor.session))
|
|
20701
|
+
reasons.push("author_session");
|
|
20702
|
+
if (row.self === true && !reasons.includes("no_session"))
|
|
20703
|
+
reasons.push("self");
|
|
20704
|
+
return reasons;
|
|
20705
|
+
};
|
|
20706
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
20707
|
+
let effective = null;
|
|
20708
|
+
for (const row of rows) {
|
|
20709
|
+
if (!effective) {
|
|
20710
|
+
effective = row;
|
|
20711
|
+
continue;
|
|
20712
|
+
}
|
|
20713
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
20714
|
+
continue;
|
|
20715
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
20716
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
20717
|
+
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
20718
|
+
continue;
|
|
20719
|
+
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
20720
|
+
continue;
|
|
20721
|
+
effective = row;
|
|
20722
|
+
}
|
|
20723
|
+
return effective;
|
|
20724
|
+
}
|
|
20725
|
+
var restackExact = new WeakMap;
|
|
20726
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
20727
|
+
if (!from || !to)
|
|
20728
|
+
return false;
|
|
20729
|
+
const exact = (row, fromHead, head) => {
|
|
20730
|
+
if (exactIn === null)
|
|
20731
|
+
return true;
|
|
20732
|
+
const cwd = exactIn;
|
|
20733
|
+
let known = restackExact.get(row);
|
|
20734
|
+
if (known === undefined) {
|
|
20735
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
20736
|
+
from: fromHead,
|
|
20737
|
+
to: head,
|
|
20738
|
+
fromBase: row.fromBase,
|
|
20739
|
+
toBase: row.toBase
|
|
20740
|
+
});
|
|
20741
|
+
restackExact.set(row, known);
|
|
20742
|
+
}
|
|
20743
|
+
return known;
|
|
20744
|
+
};
|
|
20745
|
+
const edges = new Map;
|
|
20746
|
+
for (const row of rows)
|
|
20747
|
+
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))
|
|
20748
|
+
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
20749
|
+
const seen = new Set([from]);
|
|
20750
|
+
const queue = [from];
|
|
20751
|
+
while (queue.length) {
|
|
20752
|
+
const next = queue.shift();
|
|
20753
|
+
if (next === to)
|
|
20754
|
+
return true;
|
|
20755
|
+
for (const target of edges.get(next) ?? [])
|
|
20756
|
+
if (!seen.has(target)) {
|
|
20757
|
+
seen.add(target);
|
|
20758
|
+
queue.push(target);
|
|
20759
|
+
}
|
|
20760
|
+
}
|
|
20761
|
+
return false;
|
|
20791
20762
|
}
|
|
20792
|
-
function
|
|
20793
|
-
const
|
|
20794
|
-
const
|
|
20795
|
-
const
|
|
20796
|
-
const
|
|
20797
|
-
const
|
|
20798
|
-
const
|
|
20799
|
-
|
|
20800
|
-
|
|
20801
|
-
|
|
20802
|
-
|
|
20803
|
-
|
|
20804
|
-
|
|
20805
|
-
|
|
20806
|
-
|
|
20807
|
-
|
|
20808
|
-
|
|
20809
|
-
|
|
20810
|
-
|
|
20811
|
-
|
|
20812
|
-
|
|
20763
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
20764
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
20765
|
+
const head = branchHead(cwd, branch);
|
|
20766
|
+
const fallbackBase = defaultBase(cwd);
|
|
20767
|
+
const scope = branchScope(cwd, branch);
|
|
20768
|
+
const keyCache = new Map;
|
|
20769
|
+
const keysAt = (base, at) => {
|
|
20770
|
+
if (!base || !at)
|
|
20771
|
+
return { patchId: null, diffHash: null };
|
|
20772
|
+
const cacheKey = `${base}\x00${at}`;
|
|
20773
|
+
let key = keyCache.get(cacheKey);
|
|
20774
|
+
if (!key) {
|
|
20775
|
+
key = { patchId: patchId(cwd, base, at), diffHash: diffHash(cwd, base, at) };
|
|
20776
|
+
keyCache.set(cacheKey, key);
|
|
20777
|
+
}
|
|
20778
|
+
return key;
|
|
20779
|
+
};
|
|
20780
|
+
const keyFor = (base) => keysAt(base, head);
|
|
20781
|
+
const driftCache = new Map;
|
|
20782
|
+
const driftOf = (row) => {
|
|
20783
|
+
if (driftCache.has(row))
|
|
20784
|
+
return driftCache.get(row) ?? null;
|
|
20785
|
+
let drift = null;
|
|
20786
|
+
const toBase = row.base ?? fallbackBase;
|
|
20787
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
20788
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
20789
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
20790
|
+
drift = verdictDrift(cwd, {
|
|
20791
|
+
from: row.head,
|
|
20792
|
+
to: head,
|
|
20793
|
+
fromBase,
|
|
20794
|
+
toBase: toBaseSha,
|
|
20795
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
20796
|
+
});
|
|
20797
|
+
driftCache.set(row, drift);
|
|
20798
|
+
return drift;
|
|
20799
|
+
};
|
|
20800
|
+
const exactCache = new Map;
|
|
20801
|
+
const exactCarry = (row) => {
|
|
20802
|
+
if (FAILING.has(String(row.result)))
|
|
20803
|
+
return true;
|
|
20804
|
+
let exact = exactCache.get(row);
|
|
20805
|
+
if (exact === undefined) {
|
|
20806
|
+
const toBase = row.base ?? fallbackBase;
|
|
20807
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
20808
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
20809
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
20810
|
+
exactCache.set(row, exact);
|
|
20811
|
+
}
|
|
20812
|
+
return exact;
|
|
20813
|
+
};
|
|
20814
|
+
const keyBasis = (row) => {
|
|
20815
|
+
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
20816
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
20817
|
+
};
|
|
20818
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
20819
|
+
const carryOf = (row) => {
|
|
20820
|
+
if (keyBasis(row) !== "stale")
|
|
20821
|
+
return null;
|
|
20822
|
+
if (restacked(row))
|
|
20823
|
+
return null;
|
|
20824
|
+
const drift = driftOf(row);
|
|
20825
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
20826
|
+
};
|
|
20827
|
+
const basisOf = (row) => {
|
|
20828
|
+
const basis = keyBasis(row);
|
|
20829
|
+
if (basis !== "stale")
|
|
20830
|
+
return basis;
|
|
20831
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
20832
|
+
};
|
|
20833
|
+
const sameCode = (failed, row) => {
|
|
20834
|
+
if (failed.head && failed.head === row.head)
|
|
20835
|
+
return true;
|
|
20836
|
+
const base = failed.base ?? fallbackBase;
|
|
20837
|
+
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
20838
|
+
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
20839
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
20840
|
+
};
|
|
20841
|
+
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
20842
|
+
const failureAuthorCache = new Map;
|
|
20843
|
+
const failureAuthors = (row) => {
|
|
20844
|
+
let set = failureAuthorCache.get(row.seq);
|
|
20845
|
+
if (!set) {
|
|
20846
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
20847
|
+
failureAuthorCache.set(row.seq, set);
|
|
20848
|
+
}
|
|
20849
|
+
return set;
|
|
20850
|
+
};
|
|
20851
|
+
const reasonCache = new Map;
|
|
20852
|
+
const reasonsOf = (row) => {
|
|
20853
|
+
let reasons = reasonCache.get(row);
|
|
20854
|
+
if (!reasons) {
|
|
20855
|
+
const failing = FAILING.has(String(row.result));
|
|
20856
|
+
reasons = [
|
|
20857
|
+
...trustReasons(cwd, row),
|
|
20858
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
20859
|
+
];
|
|
20860
|
+
reasonCache.set(row, reasons);
|
|
20861
|
+
}
|
|
20862
|
+
return reasons;
|
|
20863
|
+
};
|
|
20864
|
+
const retired = new Set;
|
|
20865
|
+
for (const row of rows)
|
|
20866
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
20867
|
+
retired.add(row.supersedes);
|
|
20868
|
+
const byKind = new Map;
|
|
20869
|
+
for (const row of rows)
|
|
20870
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
20871
|
+
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
20872
|
+
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
20873
|
+
}
|
|
20874
|
+
const verdicts = [];
|
|
20875
|
+
const warnings = [];
|
|
20876
|
+
for (const [kind, list] of byKind) {
|
|
20877
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
20878
|
+
if (!row)
|
|
20879
|
+
continue;
|
|
20880
|
+
const basis = basisOf(row);
|
|
20881
|
+
const current = basis !== "stale";
|
|
20882
|
+
const independence = reasonsOf(row);
|
|
20883
|
+
const reasons = [
|
|
20884
|
+
...current ? [] : ["stale"],
|
|
20885
|
+
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
20886
|
+
...independence
|
|
20887
|
+
];
|
|
20888
|
+
verdicts.push({
|
|
20889
|
+
kind,
|
|
20890
|
+
basis,
|
|
20891
|
+
current,
|
|
20892
|
+
independent: independence.length === 0,
|
|
20893
|
+
accepted: reasons.length === 0,
|
|
20894
|
+
reasons,
|
|
20895
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
20896
|
+
verdict: row
|
|
20897
|
+
});
|
|
20898
|
+
if (reasons.length === 0) {
|
|
20899
|
+
for (const later of list)
|
|
20900
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
20901
|
+
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`);
|
|
20902
|
+
}
|
|
20813
20903
|
}
|
|
20814
|
-
|
|
20904
|
+
for (const row of rows)
|
|
20905
|
+
if (row.integrity === "unverified" && scope(row))
|
|
20906
|
+
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)`);
|
|
20907
|
+
const tampered = ledgerTampering(cwd);
|
|
20908
|
+
if (tampered.length) {
|
|
20909
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
20910
|
+
for (const entry of verdicts) {
|
|
20911
|
+
entry.accepted = false;
|
|
20912
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
20913
|
+
}
|
|
20914
|
+
}
|
|
20915
|
+
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
20916
|
+
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
20917
|
+
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
20918
|
+
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
20919
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
20920
|
+
accepted: false,
|
|
20921
|
+
verdict: failing?.verdict ?? null,
|
|
20922
|
+
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
20923
|
+
};
|
|
20924
|
+
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))));
|
|
20925
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
20926
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
20927
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
20928
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
20929
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
20815
20930
|
return {
|
|
20816
|
-
|
|
20817
|
-
|
|
20818
|
-
|
|
20819
|
-
|
|
20820
|
-
|
|
20821
|
-
base,
|
|
20822
|
-
|
|
20931
|
+
branch,
|
|
20932
|
+
head,
|
|
20933
|
+
review: accepted.accepted ? "verified" : selfEntry ? "self-reviewed" : "unreviewed",
|
|
20934
|
+
selfVerdict: accepted.accepted ? null : selfEntry?.verdict ?? null,
|
|
20935
|
+
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
20936
|
+
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
20937
|
+
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
20938
|
+
carry: carryEntry?.carry ?? null,
|
|
20939
|
+
reverify,
|
|
20940
|
+
accepted,
|
|
20941
|
+
verdicts,
|
|
20942
|
+
authors: [...authors].toSorted(),
|
|
20943
|
+
warnings
|
|
20823
20944
|
};
|
|
20824
20945
|
}
|
|
20825
|
-
var
|
|
20946
|
+
var OBSERVED_ONLY = new Set([
|
|
20947
|
+
"check",
|
|
20948
|
+
"merge.unverified",
|
|
20949
|
+
"policy.judged",
|
|
20950
|
+
...PR_ROW_TYPES
|
|
20951
|
+
]);
|
|
20826
20952
|
|
|
20827
20953
|
// packages/workit-core/src/core/policy/derive.ts
|
|
20828
20954
|
var RULES = {
|
|
@@ -20977,7 +21103,7 @@ var productDecision = (task, ledger) => {
|
|
|
20977
21103
|
};
|
|
20978
21104
|
};
|
|
20979
21105
|
var planRecorded = (task, root) => {
|
|
20980
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(
|
|
21106
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path16.resolve(root, ref.path)));
|
|
20981
21107
|
if (cited)
|
|
20982
21108
|
return {
|
|
20983
21109
|
met: true,
|
|
@@ -21044,14 +21170,14 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
21044
21170
|
|
|
21045
21171
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
21046
21172
|
var digestBytes = (value) => createHash10("sha256").update(value).digest("hex");
|
|
21047
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
21048
|
-
var relative = (root, candidate) =>
|
|
21173
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path17.sep}`);
|
|
21174
|
+
var relative = (root, candidate) => path17.relative(root, candidate).split(path17.sep).join("/") || ".";
|
|
21049
21175
|
var canonicalPath = (value) => {
|
|
21050
21176
|
if (typeof value !== "string" || !value)
|
|
21051
21177
|
return null;
|
|
21052
21178
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
21053
21179
|
return null;
|
|
21054
|
-
const normalized =
|
|
21180
|
+
const normalized = path17.posix.normalize(value);
|
|
21055
21181
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
21056
21182
|
return null;
|
|
21057
21183
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -21087,7 +21213,7 @@ var compareCodeUnits2 = (left, right) => {
|
|
|
21087
21213
|
return left.length - right.length;
|
|
21088
21214
|
};
|
|
21089
21215
|
var gitPaths = (root) => {
|
|
21090
|
-
const result =
|
|
21216
|
+
const result = spawnSync10("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
21091
21217
|
cwd: root,
|
|
21092
21218
|
encoding: "buffer"
|
|
21093
21219
|
});
|
|
@@ -21099,21 +21225,21 @@ var gitPaths = (root) => {
|
|
|
21099
21225
|
git: !/not a git repository/i.test(stderr)
|
|
21100
21226
|
};
|
|
21101
21227
|
}
|
|
21102
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
21103
|
-
const stagedDeleted =
|
|
21228
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")) : [];
|
|
21229
|
+
const stagedDeleted = spawnSync10("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
21104
21230
|
if (stagedDeleted.status !== 0) {
|
|
21105
21231
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
21106
21232
|
if (!/not a git repository/i.test(stderr))
|
|
21107
21233
|
return { paths, uncertain: true, git: true };
|
|
21108
21234
|
} else if (stagedDeleted.stdout) {
|
|
21109
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
21235
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path17.sep).join("/")));
|
|
21110
21236
|
}
|
|
21111
21237
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
21112
21238
|
};
|
|
21113
21239
|
var walk = (root, directory, output) => {
|
|
21114
21240
|
let names;
|
|
21115
21241
|
try {
|
|
21116
|
-
names =
|
|
21242
|
+
names = fs13.readdirSync(directory);
|
|
21117
21243
|
} catch {
|
|
21118
21244
|
return true;
|
|
21119
21245
|
}
|
|
@@ -21121,10 +21247,10 @@ var walk = (root, directory, output) => {
|
|
|
21121
21247
|
for (const name of names) {
|
|
21122
21248
|
if (name === ".git" || name === ".workit")
|
|
21123
21249
|
continue;
|
|
21124
|
-
const target =
|
|
21250
|
+
const target = path17.join(directory, name);
|
|
21125
21251
|
let stat;
|
|
21126
21252
|
try {
|
|
21127
|
-
stat =
|
|
21253
|
+
stat = fs13.lstatSync(target);
|
|
21128
21254
|
} catch {
|
|
21129
21255
|
uncertain = true;
|
|
21130
21256
|
continue;
|
|
@@ -21139,14 +21265,14 @@ var walk = (root, directory, output) => {
|
|
|
21139
21265
|
var scopeRoots = (root, scope) => {
|
|
21140
21266
|
const roots = [];
|
|
21141
21267
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
21142
|
-
const target =
|
|
21268
|
+
const target = path17.resolve(root, item);
|
|
21143
21269
|
if (!inside(root, target))
|
|
21144
21270
|
return failure2("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
|
|
21145
21271
|
let ancestor = target;
|
|
21146
|
-
while (!
|
|
21147
|
-
ancestor =
|
|
21272
|
+
while (!fs13.existsSync(ancestor) && ancestor !== root)
|
|
21273
|
+
ancestor = path17.dirname(ancestor);
|
|
21148
21274
|
try {
|
|
21149
|
-
if (!inside(root,
|
|
21275
|
+
if (!inside(root, fs13.realpathSync(ancestor)))
|
|
21150
21276
|
return failure2("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
|
|
21151
21277
|
} catch {
|
|
21152
21278
|
return failure2("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
@@ -21158,7 +21284,7 @@ var scopeRoots = (root, scope) => {
|
|
|
21158
21284
|
function captureCandidate(root, scope, environment = []) {
|
|
21159
21285
|
let checkout;
|
|
21160
21286
|
try {
|
|
21161
|
-
checkout =
|
|
21287
|
+
checkout = fs13.realpathSync(root);
|
|
21162
21288
|
} catch {
|
|
21163
21289
|
return failure2("invalid_input", "candidate root does not exist");
|
|
21164
21290
|
}
|
|
@@ -21176,11 +21302,11 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21176
21302
|
if (item === ".git" || item === ".workit")
|
|
21177
21303
|
continue;
|
|
21178
21304
|
names.add(item);
|
|
21179
|
-
if (!
|
|
21305
|
+
if (!fs13.existsSync(target))
|
|
21180
21306
|
continue;
|
|
21181
21307
|
let stat;
|
|
21182
21308
|
try {
|
|
21183
|
-
stat =
|
|
21309
|
+
stat = fs13.lstatSync(target);
|
|
21184
21310
|
} catch {
|
|
21185
21311
|
uncertain = true;
|
|
21186
21312
|
continue;
|
|
@@ -21196,17 +21322,17 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21196
21322
|
for (const item of [...names].toSorted()) {
|
|
21197
21323
|
if (!scopeMatches(item, normalizedScope))
|
|
21198
21324
|
continue;
|
|
21199
|
-
const target =
|
|
21325
|
+
const target = path17.join(checkout, item);
|
|
21200
21326
|
try {
|
|
21201
|
-
const stat =
|
|
21327
|
+
const stat = fs13.lstatSync(target);
|
|
21202
21328
|
if (stat.isSymbolicLink()) {
|
|
21203
|
-
const link =
|
|
21329
|
+
const link = fs13.readlinkSync(target);
|
|
21204
21330
|
files.push({ path: item, kind: "symlink", digest: digestBytes(link), executable: null });
|
|
21205
21331
|
} else if (stat.isFile()) {
|
|
21206
21332
|
files.push({
|
|
21207
21333
|
path: item,
|
|
21208
21334
|
kind: "file",
|
|
21209
|
-
digest: digestBytes(
|
|
21335
|
+
digest: digestBytes(fs13.readFileSync(target)),
|
|
21210
21336
|
executable: (stat.mode & 73) !== 0
|
|
21211
21337
|
});
|
|
21212
21338
|
} else if (!stat.isDirectory()) {
|
|
@@ -21231,7 +21357,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
21231
21357
|
const values = supplied.toSorted((left, right) => compareCodeUnits2(left.name, right.name)).map(({ name, value }) => ({ name, value, refs: [] }));
|
|
21232
21358
|
if (values.some(({ value }) => value === null))
|
|
21233
21359
|
completeness = "uncertain";
|
|
21234
|
-
const headResult =
|
|
21360
|
+
const headResult = spawnSync10("git", ["rev-parse", "HEAD"], { cwd: checkout, encoding: "utf8" });
|
|
21235
21361
|
if (headResult.status !== 0 && !/not a git repository/i.test(headResult.stderr ?? ""))
|
|
21236
21362
|
completeness = "uncertain";
|
|
21237
21363
|
const head = headResult.status === 0 ? headResult.stdout.trim() : null;
|
|
@@ -21371,7 +21497,7 @@ var findingVerificationPasses = (findingCandidateId, evidence) => evidence.statu
|
|
|
21371
21497
|
var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
21372
21498
|
let root;
|
|
21373
21499
|
try {
|
|
21374
|
-
root =
|
|
21500
|
+
root = fs13.realpathSync(checkoutRoot);
|
|
21375
21501
|
} catch {
|
|
21376
21502
|
return failure2("permission_denied", "checkout root is unavailable");
|
|
21377
21503
|
}
|
|
@@ -21380,23 +21506,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
21380
21506
|
continue;
|
|
21381
21507
|
if (!reference.digest)
|
|
21382
21508
|
return failure2("invalid_input", "document references require a byte digest");
|
|
21383
|
-
const target =
|
|
21384
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
21509
|
+
const target = path17.resolve(root, reference.path);
|
|
21510
|
+
if (target !== root && !target.startsWith(`${root}${path17.sep}`))
|
|
21385
21511
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
21386
21512
|
fields: [
|
|
21387
21513
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
21388
21514
|
]
|
|
21389
21515
|
});
|
|
21390
21516
|
try {
|
|
21391
|
-
const below =
|
|
21517
|
+
const below = path17.relative(root, target);
|
|
21392
21518
|
let current = root;
|
|
21393
|
-
for (const segment of below.split(
|
|
21519
|
+
for (const segment of below.split(path17.sep)) {
|
|
21394
21520
|
if (!segment)
|
|
21395
21521
|
continue;
|
|
21396
|
-
current =
|
|
21397
|
-
const stat =
|
|
21398
|
-
const real =
|
|
21399
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
21522
|
+
current = path17.join(current, segment);
|
|
21523
|
+
const stat = fs13.lstatSync(current);
|
|
21524
|
+
const real = fs13.realpathSync(current);
|
|
21525
|
+
if (real !== root && !real.startsWith(`${root}${path17.sep}`))
|
|
21400
21526
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
21401
21527
|
fields: [
|
|
21402
21528
|
{
|
|
@@ -21412,7 +21538,7 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
21412
21538
|
if (current !== target && !stat.isDirectory())
|
|
21413
21539
|
return failure2("invalid_input", "document reference ancestor is not a directory");
|
|
21414
21540
|
}
|
|
21415
|
-
if (digestBytes(
|
|
21541
|
+
if (digestBytes(fs13.readFileSync(target)) !== reference.digest)
|
|
21416
21542
|
return failure2("permission_denied", "decision document bytes have changed");
|
|
21417
21543
|
} catch {
|
|
21418
21544
|
return failure2("invalid_input", "decision document is unavailable");
|
|
@@ -22255,7 +22381,7 @@ var judgeInputSummary = (raw) => {
|
|
|
22255
22381
|
|
|
22256
22382
|
// packages/workit-core/src/autonomy.ts
|
|
22257
22383
|
import os4 from "node:os";
|
|
22258
|
-
import
|
|
22384
|
+
import path18 from "node:path";
|
|
22259
22385
|
var accountHome = () => {
|
|
22260
22386
|
try {
|
|
22261
22387
|
return os4.userInfo().homedir;
|
|
@@ -22264,10 +22390,10 @@ var accountHome = () => {
|
|
|
22264
22390
|
}
|
|
22265
22391
|
};
|
|
22266
22392
|
var grantsHome = { resolve: accountHome };
|
|
22267
|
-
var grantsDir = () =>
|
|
22393
|
+
var grantsDir = () => path18.join(grantsHome.resolve(), ".config", "workit");
|
|
22268
22394
|
var grantsOverride = () => {
|
|
22269
|
-
const configured =
|
|
22270
|
-
return configured ===
|
|
22395
|
+
const configured = path18.resolve(resolveConfigDir());
|
|
22396
|
+
return configured === path18.resolve(grantsDir()) ? null : `the config directory is redirected to ${configured} (WORKFLOW_TOOLKIT_CONFIG, WORKFLOW_TOOLKIT_CONFIG_DIR or XDG_CONFIG_HOME); grants are read only from ${path18.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
22271
22397
|
};
|
|
22272
22398
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
22273
22399
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -24076,9 +24202,9 @@ class WorkitCore {
|
|
|
24076
24202
|
}
|
|
24077
24203
|
}
|
|
24078
24204
|
// packages/workit-core/src/core/repo-context.ts
|
|
24079
|
-
import { spawnSync as
|
|
24205
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
24080
24206
|
import { existsSync as existsSync7, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
24081
|
-
import
|
|
24207
|
+
import path19 from "node:path";
|
|
24082
24208
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
24083
24209
|
try {
|
|
24084
24210
|
gitRevisionParts(value);
|
|
@@ -24105,7 +24231,7 @@ var invalidRange = (cwd) => ({
|
|
|
24105
24231
|
cwd
|
|
24106
24232
|
});
|
|
24107
24233
|
var runGit = (cwd, args) => {
|
|
24108
|
-
const result =
|
|
24234
|
+
const result = spawnSync11("git", args, { cwd, encoding: "utf8" });
|
|
24109
24235
|
return {
|
|
24110
24236
|
stdout: (result.stdout ?? "").trimEnd(),
|
|
24111
24237
|
stderr: (result.stderr ?? "").trimEnd(),
|
|
@@ -24212,7 +24338,7 @@ var findPrTemplate = (cwd) => {
|
|
|
24212
24338
|
]) {
|
|
24213
24339
|
const parts = rel.split("/");
|
|
24214
24340
|
const base = parts[parts.length - 1];
|
|
24215
|
-
const dir = parts.length > 1 ?
|
|
24341
|
+
const dir = parts.length > 1 ? path19.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
24216
24342
|
const actual = probeCaseInsensitive(dir, base);
|
|
24217
24343
|
if (actual)
|
|
24218
24344
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -24243,7 +24369,7 @@ var diffStatForRange = (cwd, range) => {
|
|
|
24243
24369
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
24244
24370
|
};
|
|
24245
24371
|
var packageScripts = (cwd, keys) => {
|
|
24246
|
-
const pkgPath =
|
|
24372
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
24247
24373
|
if (!existsSync7(pkgPath))
|
|
24248
24374
|
return [];
|
|
24249
24375
|
try {
|
|
@@ -24269,9 +24395,9 @@ var documentationFiles = (cwd) => {
|
|
|
24269
24395
|
if (entry.isDirectory()) {
|
|
24270
24396
|
if (excluded.has(entry.name))
|
|
24271
24397
|
continue;
|
|
24272
|
-
walk(
|
|
24398
|
+
walk(path19.join(dir, entry.name), depth + 1);
|
|
24273
24399
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
24274
|
-
matches.push("./" +
|
|
24400
|
+
matches.push("./" + path19.relative(cwd, path19.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
24275
24401
|
}
|
|
24276
24402
|
}
|
|
24277
24403
|
};
|
|
@@ -24287,7 +24413,7 @@ var readTrimmed2 = (file, maxLines) => {
|
|
|
24287
24413
|
`);
|
|
24288
24414
|
};
|
|
24289
24415
|
function prReadyContext(root, range) {
|
|
24290
|
-
const cwd =
|
|
24416
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
24291
24417
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
24292
24418
|
return invalidRange(cwd);
|
|
24293
24419
|
let stdout = "";
|
|
@@ -24345,7 +24471,7 @@ ${status.stderr}` : "");
|
|
|
24345
24471
|
stdout += `template_path: ${template}
|
|
24346
24472
|
|
|
24347
24473
|
`;
|
|
24348
|
-
stdout += readTrimmed2(
|
|
24474
|
+
stdout += readTrimmed2(path19.join(cwd, template), 220);
|
|
24349
24475
|
} else {
|
|
24350
24476
|
stdout += `template_path: none
|
|
24351
24477
|
|
|
@@ -24355,7 +24481,7 @@ ${status.stderr}` : "");
|
|
|
24355
24481
|
stdout += `
|
|
24356
24482
|
`;
|
|
24357
24483
|
stdout += printSection("Recent Validation Signals");
|
|
24358
|
-
if (existsSync7(
|
|
24484
|
+
if (existsSync7(path19.join(cwd, "package.json"))) {
|
|
24359
24485
|
stdout += `package.json detected. Common scripts:
|
|
24360
24486
|
`;
|
|
24361
24487
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -24364,7 +24490,7 @@ ${status.stderr}` : "");
|
|
|
24364
24490
|
`) + `
|
|
24365
24491
|
`;
|
|
24366
24492
|
}
|
|
24367
|
-
if (existsSync7(
|
|
24493
|
+
if (existsSync7(path19.join(cwd, "Cargo.toml")) || existsSync7(path19.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
24368
24494
|
stdout += `Rust project detected.
|
|
24369
24495
|
`;
|
|
24370
24496
|
}
|
|
@@ -24397,7 +24523,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
24397
24523
|
- Apply with the host-native editor, preserving the repository changelog conventions.
|
|
24398
24524
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
24399
24525
|
function changelogContext(root, range) {
|
|
24400
|
-
const cwd =
|
|
24526
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
24401
24527
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
24402
24528
|
return invalidRange(cwd);
|
|
24403
24529
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -24415,7 +24541,7 @@ function changelogContext(root, range) {
|
|
|
24415
24541
|
stdout += CHANGELOG_RULES + `
|
|
24416
24542
|
`;
|
|
24417
24543
|
stdout += printSection("Existing CHANGELOG.md");
|
|
24418
|
-
const changelogPath =
|
|
24544
|
+
const changelogPath = path19.join(cwd, "CHANGELOG.md");
|
|
24419
24545
|
if (existsSync7(changelogPath)) {
|
|
24420
24546
|
stdout += readTrimmed2(changelogPath, 260) + `
|
|
24421
24547
|
`;
|
|
@@ -24435,7 +24561,7 @@ function changelogContext(root, range) {
|
|
|
24435
24561
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
24436
24562
|
}
|
|
24437
24563
|
function docsRefreshContext(root, range) {
|
|
24438
|
-
const cwd =
|
|
24564
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
24439
24565
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
24440
24566
|
return invalidRange(cwd);
|
|
24441
24567
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -24457,7 +24583,7 @@ function docsRefreshContext(root, range) {
|
|
|
24457
24583
|
`) + `
|
|
24458
24584
|
`;
|
|
24459
24585
|
stdout += printSection("README Preview");
|
|
24460
|
-
const readme =
|
|
24586
|
+
const readme = path19.join(cwd, "README.md");
|
|
24461
24587
|
if (existsSync7(readme)) {
|
|
24462
24588
|
stdout += readTrimmed2(readme, 220) + `
|
|
24463
24589
|
`;
|
|
@@ -24466,7 +24592,7 @@ function docsRefreshContext(root, range) {
|
|
|
24466
24592
|
`;
|
|
24467
24593
|
}
|
|
24468
24594
|
stdout += printSection("Package Scripts");
|
|
24469
|
-
const pkgPath =
|
|
24595
|
+
const pkgPath = path19.join(cwd, "package.json");
|
|
24470
24596
|
if (existsSync7(pkgPath)) {
|
|
24471
24597
|
try {
|
|
24472
24598
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -24480,7 +24606,7 @@ function docsRefreshContext(root, range) {
|
|
|
24480
24606
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
24481
24607
|
}
|
|
24482
24608
|
function releaseNotesContext(root, rangeOrTag) {
|
|
24483
|
-
const cwd =
|
|
24609
|
+
const cwd = path19.resolve(repoRoot(root));
|
|
24484
24610
|
if (!rangeOrTag) {
|
|
24485
24611
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
24486
24612
|
`, exitCode: 1, cwd };
|
|
@@ -24518,7 +24644,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
24518
24644
|
`;
|
|
24519
24645
|
stdout += printSection("Existing Release Files");
|
|
24520
24646
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
24521
|
-
if (existsSync7(
|
|
24647
|
+
if (existsSync7(path19.join(cwd, rel)))
|
|
24522
24648
|
stdout += rel + `
|
|
24523
24649
|
`;
|
|
24524
24650
|
}
|
|
@@ -24547,6 +24673,46 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
24547
24673
|
|
|
24548
24674
|
// packages/workit-core/src/core/tracker-issues.ts
|
|
24549
24675
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
24676
|
+
|
|
24677
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
24678
|
+
import fs14 from "node:fs";
|
|
24679
|
+
import path20 from "node:path";
|
|
24680
|
+
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
24681
|
+
// packages/workit-core/src/forge/pr-body.ts
|
|
24682
|
+
function parseGhIssue(value) {
|
|
24683
|
+
const m = /issues\/(\d+)/.exec(value);
|
|
24684
|
+
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
24685
|
+
}
|
|
24686
|
+
|
|
24687
|
+
// packages/workit-core/src/core/pr-create.ts
|
|
24688
|
+
function whichOnPath(tool) {
|
|
24689
|
+
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
24690
|
+
for (const dir of (process.env.PATH ?? "").split(path20.delimiter)) {
|
|
24691
|
+
if (!dir)
|
|
24692
|
+
continue;
|
|
24693
|
+
for (const name of names) {
|
|
24694
|
+
const candidate = path20.join(dir, name);
|
|
24695
|
+
try {
|
|
24696
|
+
fs14.accessSync(candidate, fs14.constants.X_OK);
|
|
24697
|
+
return candidate;
|
|
24698
|
+
} catch {}
|
|
24699
|
+
}
|
|
24700
|
+
}
|
|
24701
|
+
return null;
|
|
24702
|
+
}
|
|
24703
|
+
var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "glab" : "gh") !== null;
|
|
24704
|
+
var pushRemote = (cwd) => {
|
|
24705
|
+
const result = spawnSync12("git", ["remote", "get-url", "--push", "--all", "origin"], {
|
|
24706
|
+
cwd,
|
|
24707
|
+
encoding: "utf8"
|
|
24708
|
+
});
|
|
24709
|
+
if (result.status !== 0)
|
|
24710
|
+
return null;
|
|
24711
|
+
const urls = (result.stdout ?? "").split(/\r?\n/u).map((url) => url.trim()).filter(Boolean);
|
|
24712
|
+
return urls.length === 1 ? urls[0] : null;
|
|
24713
|
+
};
|
|
24714
|
+
|
|
24715
|
+
// packages/workit-core/src/core/tracker-issues.ts
|
|
24550
24716
|
var repoPathFromRemote = (remote) => {
|
|
24551
24717
|
let rest = (remote || "").trim().replace(/\/+$/, "");
|
|
24552
24718
|
if (!rest)
|
|
@@ -27810,6 +27976,25 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
27810
27976
|
var signals = new Map;
|
|
27811
27977
|
var cache = new Map;
|
|
27812
27978
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
27979
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
27980
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
27981
|
+
"git",
|
|
27982
|
+
"pr",
|
|
27983
|
+
"ci",
|
|
27984
|
+
"stack",
|
|
27985
|
+
"release",
|
|
27986
|
+
"task",
|
|
27987
|
+
"ledger",
|
|
27988
|
+
"evidence",
|
|
27989
|
+
"finding",
|
|
27990
|
+
"decision",
|
|
27991
|
+
"worker",
|
|
27992
|
+
"verify-delivery",
|
|
27993
|
+
"policy",
|
|
27994
|
+
"doctor",
|
|
27995
|
+
"help"
|
|
27996
|
+
];
|
|
27997
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
27813
27998
|
// packages/workit-core/src/core/heavy.ts
|
|
27814
27999
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
27815
28000
|
var sub = (...names) => new Set(names);
|
|
@@ -28018,6 +28203,10 @@ var WRITE_TOOLS = new Set([
|
|
|
28018
28203
|
// packages/workit-core/src/hooks/standing.ts
|
|
28019
28204
|
var sinceStart = new Map;
|
|
28020
28205
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
28206
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
28207
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
28208
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
28209
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
28021
28210
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
28022
28211
|
|
|
28023
28212
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
@@ -28186,6 +28375,92 @@ var codexCapabilities = (host, availability = {}) => capabilitiesFor(codexDescri
|
|
|
28186
28375
|
if (false)
|
|
28187
28376
|
;
|
|
28188
28377
|
|
|
28378
|
+
// packages/workit-core/src/core/role-models.ts
|
|
28379
|
+
var MODEL_ROLES = [
|
|
28380
|
+
"decider",
|
|
28381
|
+
"implementer",
|
|
28382
|
+
"verifier",
|
|
28383
|
+
"reviewer",
|
|
28384
|
+
"explore",
|
|
28385
|
+
"babysit"
|
|
28386
|
+
];
|
|
28387
|
+
var MODEL_TIERS = ["mundane", "standard", "hard"];
|
|
28388
|
+
var HOST_KEYS = new Set([
|
|
28389
|
+
"claude_code",
|
|
28390
|
+
"opencode",
|
|
28391
|
+
"codex",
|
|
28392
|
+
"codex_cli",
|
|
28393
|
+
"codex_desktop",
|
|
28394
|
+
"cursor",
|
|
28395
|
+
"pi",
|
|
28396
|
+
"default"
|
|
28397
|
+
]);
|
|
28398
|
+
var BUILT_IN_CLAUDE_CODE = {
|
|
28399
|
+
decider: { model: "fable", effort: "high" },
|
|
28400
|
+
"implementer.hard": { model: "opus" },
|
|
28401
|
+
"implementer.standard": { model: "sonnet" },
|
|
28402
|
+
"implementer.mundane": { model: "haiku" },
|
|
28403
|
+
verifier: { model: "sonnet" },
|
|
28404
|
+
reviewer: { model: "opus" },
|
|
28405
|
+
explore: { model: "haiku" },
|
|
28406
|
+
babysit: { model: "haiku" }
|
|
28407
|
+
};
|
|
28408
|
+
var isObject7 = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
28409
|
+
var name = (value) => typeof value === "string" && value.trim() ? value.trim() : null;
|
|
28410
|
+
var leaf = (value) => {
|
|
28411
|
+
const text = name(value);
|
|
28412
|
+
if (text)
|
|
28413
|
+
return { model: text, effort: null };
|
|
28414
|
+
if (!isObject7(value))
|
|
28415
|
+
return null;
|
|
28416
|
+
const model = name(value.model);
|
|
28417
|
+
return model ? { model, effort: name(value.effort) } : null;
|
|
28418
|
+
};
|
|
28419
|
+
var isFlat = (value) => typeof value === "string" || isObject7(value) && Object.hasOwn(value, "model");
|
|
28420
|
+
var forHost = (value, host) => {
|
|
28421
|
+
if (isFlat(value))
|
|
28422
|
+
return leaf(value);
|
|
28423
|
+
if (!isObject7(value))
|
|
28424
|
+
return null;
|
|
28425
|
+
const family = host ? hostFamily(host) : null;
|
|
28426
|
+
for (const key of [family, host, "default"])
|
|
28427
|
+
if (key && Object.hasOwn(value, key)) {
|
|
28428
|
+
const found = leaf(value[key]);
|
|
28429
|
+
if (found)
|
|
28430
|
+
return found;
|
|
28431
|
+
}
|
|
28432
|
+
return null;
|
|
28433
|
+
};
|
|
28434
|
+
var rolesOf = (models) => isObject7(models) && isObject7(models.roles) ? models.roles : null;
|
|
28435
|
+
var resolveRoleModel = (cwd, selector, host, dir) => {
|
|
28436
|
+
const { role, tier } = selector;
|
|
28437
|
+
for (const source of modelSources(cwd, dir)) {
|
|
28438
|
+
const where = source.global ? "the global default" : `workspace ${source.name}`;
|
|
28439
|
+
const roles = rolesOf(source.models);
|
|
28440
|
+
const configured = roles && Object.hasOwn(roles, role) ? roles[role] : undefined;
|
|
28441
|
+
const fromRoles = role === "implementer" ? tier && isObject7(configured) && Object.hasOwn(configured, tier) ? forHost(configured[tier], host) : null : forHost(configured, host);
|
|
28442
|
+
if (fromRoles)
|
|
28443
|
+
return { ...fromRoles, source: `models.roles in ${where}` };
|
|
28444
|
+
if (role === "implementer" && tier && isObject7(source.tierModels)) {
|
|
28445
|
+
const fromTiers = Object.hasOwn(source.tierModels, tier) ? forHost(source.tierModels[tier], host) : null;
|
|
28446
|
+
if (fromTiers)
|
|
28447
|
+
return { model: fromTiers.model, effort: null, source: `fanout.tierModels in ${where}` };
|
|
28448
|
+
}
|
|
28449
|
+
}
|
|
28450
|
+
if (host && hostFamily(host) === "claude_code") {
|
|
28451
|
+
const builtIn = BUILT_IN_CLAUDE_CODE[tier ? `${role}.${tier}` : role];
|
|
28452
|
+
if (builtIn)
|
|
28453
|
+
return { model: builtIn.model, effort: builtIn.effort ?? null, source: "built-in" };
|
|
28454
|
+
}
|
|
28455
|
+
return null;
|
|
28456
|
+
};
|
|
28457
|
+
var uniformImplementerModel = (cwd, host, dir) => {
|
|
28458
|
+
const tiers = MODEL_TIERS.map((tier) => resolveRoleModel(cwd, { role: "implementer", tier }, host, dir));
|
|
28459
|
+
const [first] = tiers;
|
|
28460
|
+
return first && tiers.every((t) => t?.model === first.model && t.effort === first.effort) ? first : null;
|
|
28461
|
+
};
|
|
28462
|
+
var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
|
|
28463
|
+
|
|
28189
28464
|
// packages/workit-codex/scripts/agents.ts
|
|
28190
28465
|
import {
|
|
28191
28466
|
existsSync as existsSync10,
|
|
@@ -28199,13 +28474,26 @@ import {
|
|
|
28199
28474
|
} from "node:fs";
|
|
28200
28475
|
import os5 from "node:os";
|
|
28201
28476
|
import path25 from "node:path";
|
|
28477
|
+
var CODEX_AGENT_ROLES = ["verifier", "reviewer", "implementer"];
|
|
28202
28478
|
var CODEX_AGENT_MARKER = "# Generated by Workit";
|
|
28479
|
+
var codexAgentName = (role) => `workit-${role}`;
|
|
28480
|
+
var codexAgentFile = (role) => `${codexAgentName(role)}.toml`;
|
|
28481
|
+
var tomlString = (value) => JSON.stringify(value);
|
|
28203
28482
|
var codexHomeOfInstall = (pluginRoot) => {
|
|
28204
28483
|
const parts = path25.resolve(pluginRoot).split(path25.sep);
|
|
28205
28484
|
const cache = parts.length - 4;
|
|
28206
28485
|
return cache >= 2 && parts[cache] === "cache" && parts[cache - 1] === "plugins" ? parts.slice(0, cache - 1).join(path25.sep) || path25.sep : null;
|
|
28207
28486
|
};
|
|
28208
28487
|
var codexHomeFor = (pluginRoot, env) => codexHomeOfInstall(pluginRoot) ?? (env.CODEX_HOME || path25.join(env.HOME || os5.homedir(), ".codex"));
|
|
28488
|
+
var withRoleModel = (content, file, models) => {
|
|
28489
|
+
const role = CODEX_AGENT_ROLES.find((candidate) => codexAgentFile(candidate) === file);
|
|
28490
|
+
const chosen = role ? models[role] : undefined;
|
|
28491
|
+
if (!chosen)
|
|
28492
|
+
return content;
|
|
28493
|
+
return content.replace(/^(description = .*)$/m, (line) => `${line}
|
|
28494
|
+
model = ${tomlString(chosen.model)}${chosen.effort ? `
|
|
28495
|
+
model_reasoning_effort = ${tomlString(chosen.effort)}` : ""}`);
|
|
28496
|
+
};
|
|
28209
28497
|
var MARKER_VERSION = /^# Generated by Workit (\d+\.\d+\.\d+)\b/;
|
|
28210
28498
|
var agentMarkerVersion = (text) => MARKER_VERSION.exec(text)?.[1] ?? null;
|
|
28211
28499
|
var stampCodexAgent = (content, version) => version && content.startsWith(CODEX_AGENT_MARKER) && !agentMarkerVersion(content) ? `${CODEX_AGENT_MARKER} ${version}${content.slice(CODEX_AGENT_MARKER.length)}` : content;
|
|
@@ -28232,7 +28520,7 @@ var lstatOrNull = (file) => {
|
|
|
28232
28520
|
return null;
|
|
28233
28521
|
}
|
|
28234
28522
|
};
|
|
28235
|
-
var installCodexAgents = (pluginRoot, codexHome) => {
|
|
28523
|
+
var installCodexAgents = (pluginRoot, codexHome, models = {}) => {
|
|
28236
28524
|
const source = path25.join(pluginRoot, "agents");
|
|
28237
28525
|
if (!existsSync10(source))
|
|
28238
28526
|
return [];
|
|
@@ -28240,7 +28528,7 @@ var installCodexAgents = (pluginRoot, codexHome) => {
|
|
|
28240
28528
|
const target = path25.join(codexHome, "agents");
|
|
28241
28529
|
const results = [];
|
|
28242
28530
|
for (const file of readdirSync5(source).filter((name) => name.endsWith(".toml")).toSorted()) {
|
|
28243
|
-
const content = stampCodexAgent(readFileSync8(path25.join(source, file), "utf8"), version);
|
|
28531
|
+
const content = withRoleModel(stampCodexAgent(readFileSync8(path25.join(source, file), "utf8"), version), file, models);
|
|
28244
28532
|
const dest = path25.join(target, file);
|
|
28245
28533
|
const stat = lstatOrNull(dest);
|
|
28246
28534
|
if (stat?.isSymbolicLink()) {
|
|
@@ -28340,12 +28628,24 @@ var codexContextProvider = (host = detectCodexSurface(process.env), root) => ({
|
|
|
28340
28628
|
}
|
|
28341
28629
|
});
|
|
28342
28630
|
var pluginRoot = () => path26.resolve(path26.dirname(fileURLToPath3(import.meta.url)), "..");
|
|
28343
|
-
var
|
|
28631
|
+
var codexRoleModels = (cwd) => {
|
|
28632
|
+
const out = {};
|
|
28633
|
+
for (const role of ["verifier", "reviewer"]) {
|
|
28634
|
+
const found = resolveRoleModel(cwd, { role }, "codex");
|
|
28635
|
+
if (found)
|
|
28636
|
+
out[role] = { model: found.model, effort: found.effort };
|
|
28637
|
+
}
|
|
28638
|
+
const implementer = uniformImplementerModel(cwd, "codex");
|
|
28639
|
+
if (implementer)
|
|
28640
|
+
out.implementer = { model: implementer.model, effort: implementer.effort };
|
|
28641
|
+
return out;
|
|
28642
|
+
};
|
|
28643
|
+
var syncCodexAgents = (root, cwd) => {
|
|
28344
28644
|
const codexHome = codexHomeOfInstall(root);
|
|
28345
28645
|
if (!codexHome)
|
|
28346
28646
|
return;
|
|
28347
28647
|
try {
|
|
28348
|
-
installCodexAgents(root, codexHome);
|
|
28648
|
+
installCodexAgents(root, codexHome, codexRoleModels(cwd ?? resolveCodexWorkspaceRoot(root, process.env) ?? process.cwd()));
|
|
28349
28649
|
} catch {}
|
|
28350
28650
|
};
|
|
28351
28651
|
if (__require.main == __require.module) {
|
|
@@ -28366,6 +28666,7 @@ if (__require.main == __require.module) {
|
|
|
28366
28666
|
export {
|
|
28367
28667
|
codexContextProvider,
|
|
28368
28668
|
codexQualification,
|
|
28669
|
+
codexRoleModels,
|
|
28369
28670
|
resolveCodexWorkspaceRoot,
|
|
28370
28671
|
syncCodexAgents
|
|
28371
28672
|
};
|