@brainervirus/workit-codex 8.23.0 → 8.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex-plugin/plugin.json +1 -1
- package/agents/workit-implementer.toml +5 -4
- package/agents/workit-verifier.toml +2 -2
- package/dist/launch-mcp.js +1136 -154
- package/dist/{workit-hook-hegafjtc.js → workit-hook-7a6v1719.js} +1368 -250
- package/dist/workit-hook.js +2 -2
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +1 -1
- package/skills/workit-fanout/references/brief.md +4 -4
- package/skills/workit-implement/SKILL.md +5 -5
- package/skills/workit-review/SKILL.md +5 -0
- package/skills/workit-ship/SKILL.md +1 -2
- /package/dist/{workit-hook-thet0x8m.js → workit-hook-vhz9yrcw.js} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
readStandingRev,
|
|
13
13
|
readStandingSeen,
|
|
14
14
|
writeStandingSeen
|
|
15
|
-
} from "./workit-hook-
|
|
15
|
+
} from "./workit-hook-vhz9yrcw.js";
|
|
16
16
|
// packages/workit-core/src/hooks/descriptor.ts
|
|
17
17
|
var support = (descriptor, axis) => {
|
|
18
18
|
if (axis.startsWith("event:"))
|
|
@@ -40,7 +40,7 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
40
40
|
// packages/workit-core/src/hooks/context.ts
|
|
41
41
|
import { createHash as createHash12 } from "node:crypto";
|
|
42
42
|
import fs13 from "node:fs";
|
|
43
|
-
import
|
|
43
|
+
import path18 from "node:path";
|
|
44
44
|
|
|
45
45
|
// packages/workit-core/src/core/methods.ts
|
|
46
46
|
var METHODS = {
|
|
@@ -115,9 +115,9 @@ Autonomy contract:
|
|
|
115
115
|
Prose is a note: only a configured check the CLI ran (\`workit check <name>\`)
|
|
116
116
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
117
117
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
118
|
-
self-reviewed, never verified. High risk,
|
|
119
|
-
merge need a verifier with its
|
|
120
|
-
\`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
118
|
+
self-reviewed, never verified. High risk, a focused or full lane and
|
|
119
|
+
merge under \`verification: self|independent\` need a verifier with its
|
|
120
|
+
own host-derived session (a subagent's \`<lead>:<child>\`); never hand-mint one (\`<lead>-v1\`).
|
|
121
121
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
122
122
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
123
123
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -125,7 +125,7 @@ Autonomy contract:
|
|
|
125
125
|
CLI first: if a step has one right answer, use the \`workit\` verb instead of
|
|
126
126
|
hand-running git, gh or glab (\`workit git|pr|ci|check|help\`;
|
|
127
127
|
else \`npx -y @brainervirus/workit-cli\`).
|
|
128
|
-
Other checkout: \`workit --cwd <dir>
|
|
128
|
+
Other checkout: \`workit <verb> --cwd <dir> …\` (verb first: host rules match it); one workit
|
|
129
129
|
verb per shell call, never \`cd <dir> && workit\`. zsh-safe: no \`echo =====\`
|
|
130
130
|
(\`printf '%s\\n' ---\`), quote globs and URLs, no variable named \`status\` or \`path\`.
|
|
131
131
|
Add Co-Authored-By via \`workit git commit --trailer "K: v" -- <paths>\`, never
|
|
@@ -137,7 +137,7 @@ authorizes an effect. Judge work once with
|
|
|
137
137
|
\`workit policy assess --judge risk= behavior= product-choice= plan=\`.
|
|
138
138
|
Across repositories, bind items to checkout, branch and endpoint, resolve
|
|
139
139
|
competing targets before a mutation, and reconcile an uncertain
|
|
140
|
-
|
|
140
|
+
effect before a retry.
|
|
141
141
|
|
|
142
142
|
Mid-task: never silently drop or resume an objective (workit-continue).
|
|
143
143
|
|
|
@@ -7174,7 +7174,7 @@ function requirementId(input) {
|
|
|
7174
7174
|
import { createHash as createHash11 } from "node:crypto";
|
|
7175
7175
|
import * as fs12 from "node:fs";
|
|
7176
7176
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
7177
|
-
import
|
|
7177
|
+
import path16 from "node:path";
|
|
7178
7178
|
|
|
7179
7179
|
// packages/workit-core/src/git/rev.ts
|
|
7180
7180
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -7337,6 +7337,144 @@ function resolveRef(cwd, ref) {
|
|
|
7337
7337
|
return null;
|
|
7338
7338
|
return line(git(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]));
|
|
7339
7339
|
}
|
|
7340
|
+
function originHeadRef(cwd) {
|
|
7341
|
+
return line(git(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]));
|
|
7342
|
+
}
|
|
7343
|
+
function ownChangeDrift(cwd, range) {
|
|
7344
|
+
const before = diffRaw(cwd, range.fromFork, range.from);
|
|
7345
|
+
const after = diffRaw(cwd, range.toFork, range.to);
|
|
7346
|
+
if (!before || !after)
|
|
7347
|
+
return null;
|
|
7348
|
+
const judged = new Map(before.map((entry) => [entry.path, entry]));
|
|
7349
|
+
const current = new Map(after.map((entry) => [entry.path, entry]));
|
|
7350
|
+
const same = (a, b) => a !== undefined && b !== undefined && a.oldMode === b.oldMode && a.oldBlob === b.oldBlob && a.newMode === b.newMode && a.newBlob === b.newBlob;
|
|
7351
|
+
return [...new Set([...judged.keys(), ...current.keys()])].filter((file) => !same(judged.get(file), current.get(file))).toSorted();
|
|
7352
|
+
}
|
|
7353
|
+
function sameOwnChange(cwd, range) {
|
|
7354
|
+
const fromFork = mergeBase(cwd, range.fromBase, range.from);
|
|
7355
|
+
const toFork = mergeBase(cwd, range.toBase, range.to);
|
|
7356
|
+
if (!fromFork || !toFork)
|
|
7357
|
+
return false;
|
|
7358
|
+
return ownChangeDrift(cwd, { from: range.from, to: range.to, fromFork, toFork })?.length === 0;
|
|
7359
|
+
}
|
|
7360
|
+
var LANE_DIFF = [
|
|
7361
|
+
"-c",
|
|
7362
|
+
"core.quotePath=false",
|
|
7363
|
+
"diff",
|
|
7364
|
+
"--no-color",
|
|
7365
|
+
"--no-ext-diff",
|
|
7366
|
+
"--no-textconv",
|
|
7367
|
+
"--no-renames"
|
|
7368
|
+
];
|
|
7369
|
+
var laneRange = (fork, head) => {
|
|
7370
|
+
if (!safeArg(fork) || head !== null && !safeArg(head))
|
|
7371
|
+
return null;
|
|
7372
|
+
return head === null ? [fork] : [fork, head];
|
|
7373
|
+
};
|
|
7374
|
+
function diffNumstat(cwd, fork, head) {
|
|
7375
|
+
const range = laneRange(fork, head);
|
|
7376
|
+
if (!range)
|
|
7377
|
+
return null;
|
|
7378
|
+
const run = git(cwd, [...LANE_DIFF, "--numstat", "-z", ...range, "--"]);
|
|
7379
|
+
if (!run.ok)
|
|
7380
|
+
return null;
|
|
7381
|
+
const out = [];
|
|
7382
|
+
for (const record of run.stdout.split("\x00")) {
|
|
7383
|
+
const match = /^(-|\d+)\t(-|\d+)\t(.+)$/su.exec(record.replace(/^\n/u, ""));
|
|
7384
|
+
if (!match)
|
|
7385
|
+
continue;
|
|
7386
|
+
const binary = match[1] === "-";
|
|
7387
|
+
out.push({
|
|
7388
|
+
path: match[3],
|
|
7389
|
+
added: binary ? 0 : Number(match[1]),
|
|
7390
|
+
deleted: binary ? 0 : Number(match[2]),
|
|
7391
|
+
binary
|
|
7392
|
+
});
|
|
7393
|
+
}
|
|
7394
|
+
return out;
|
|
7395
|
+
}
|
|
7396
|
+
function diffRaw(cwd, fork, head) {
|
|
7397
|
+
const range = laneRange(fork, head);
|
|
7398
|
+
if (!range)
|
|
7399
|
+
return null;
|
|
7400
|
+
const run = git(cwd, [...LANE_DIFF, "--raw", "-z", "--no-abbrev", ...range, "--"]);
|
|
7401
|
+
if (!run.ok)
|
|
7402
|
+
return null;
|
|
7403
|
+
const fields = run.stdout.split("\x00");
|
|
7404
|
+
const out = [];
|
|
7405
|
+
for (let index = 0;index + 1 < fields.length; index += 2) {
|
|
7406
|
+
const meta = /^:(\d{6}) (\d{6}) ([0-9a-f]+) ([0-9a-f]+) [A-Z]\d*$/u.exec(fields[index].replace(/^\n/u, ""));
|
|
7407
|
+
if (!meta)
|
|
7408
|
+
return null;
|
|
7409
|
+
out.push({
|
|
7410
|
+
path: fields[index + 1],
|
|
7411
|
+
oldMode: meta[1],
|
|
7412
|
+
newMode: meta[2],
|
|
7413
|
+
oldBlob: meta[3],
|
|
7414
|
+
newBlob: meta[4]
|
|
7415
|
+
});
|
|
7416
|
+
}
|
|
7417
|
+
return out;
|
|
7418
|
+
}
|
|
7419
|
+
function readBlobs(cwd, ids) {
|
|
7420
|
+
const out = new Map;
|
|
7421
|
+
const wanted = [...new Set(ids)].filter((id) => /^[0-9a-f]{40,64}$/u.test(id));
|
|
7422
|
+
if (!wanted.length)
|
|
7423
|
+
return out;
|
|
7424
|
+
const run = spawnSync2("git", ["cat-file", "--batch"], {
|
|
7425
|
+
cwd,
|
|
7426
|
+
input: `${wanted.join(`
|
|
7427
|
+
`)}
|
|
7428
|
+
`,
|
|
7429
|
+
maxBuffer: MAX_BUFFER,
|
|
7430
|
+
timeout: GIT_TIMEOUTS.local,
|
|
7431
|
+
killSignal: "SIGKILL",
|
|
7432
|
+
windowsHide: true
|
|
7433
|
+
});
|
|
7434
|
+
if (run.status !== 0 || !run.stdout)
|
|
7435
|
+
return null;
|
|
7436
|
+
const bytes = run.stdout;
|
|
7437
|
+
let at = 0;
|
|
7438
|
+
for (const id of wanted) {
|
|
7439
|
+
const end = bytes.indexOf(10, at);
|
|
7440
|
+
if (end < 0)
|
|
7441
|
+
return null;
|
|
7442
|
+
const header = bytes.subarray(at, end).toString("utf8").split(" ");
|
|
7443
|
+
at = end + 1;
|
|
7444
|
+
if (header[1] === "missing")
|
|
7445
|
+
continue;
|
|
7446
|
+
const size = Number(header[2]);
|
|
7447
|
+
if (header[0] !== id || !Number.isSafeInteger(size))
|
|
7448
|
+
return null;
|
|
7449
|
+
out.set(id, bytes.subarray(at, at + size));
|
|
7450
|
+
at += size + 1;
|
|
7451
|
+
}
|
|
7452
|
+
return out;
|
|
7453
|
+
}
|
|
7454
|
+
function treeFilesNamed(cwd, rev, name) {
|
|
7455
|
+
if (!safeArg(rev))
|
|
7456
|
+
return null;
|
|
7457
|
+
const run = git(cwd, ["ls-tree", "-r", "-z", "--full-tree", rev]);
|
|
7458
|
+
if (!run.ok)
|
|
7459
|
+
return null;
|
|
7460
|
+
const out = new Map;
|
|
7461
|
+
for (const record of run.stdout.split("\x00")) {
|
|
7462
|
+
const match = /^\d{6} blob ([0-9a-f]+)\t(.+)$/su.exec(record);
|
|
7463
|
+
if (match && (match[2] === name || match[2].endsWith(`/${name}`)))
|
|
7464
|
+
out.set(match[2], match[1]);
|
|
7465
|
+
}
|
|
7466
|
+
return out;
|
|
7467
|
+
}
|
|
7468
|
+
function fileAt(cwd, rev, file) {
|
|
7469
|
+
if (!safeArg(rev))
|
|
7470
|
+
return null;
|
|
7471
|
+
const run = git(cwd, ["cat-file", "blob", `${rev}:${file}`]);
|
|
7472
|
+
return run.ok ? run.stdout : null;
|
|
7473
|
+
}
|
|
7474
|
+
function untrackedFiles(cwd) {
|
|
7475
|
+
const run = git(cwd, ["ls-files", "-z", "--others", "--exclude-standard"]);
|
|
7476
|
+
return run.ok ? run.stdout.split("\x00").filter(Boolean) : [];
|
|
7477
|
+
}
|
|
7340
7478
|
var config2 = (cwd, key) => line(git(cwd, ["config", "--get", key]));
|
|
7341
7479
|
function currentBranch(cwd) {
|
|
7342
7480
|
return line(git(cwd, ["symbolic-ref", "--short", "-q", "HEAD"]));
|
|
@@ -8329,7 +8467,7 @@ var workspaceConfigSchema = object({
|
|
|
8329
8467
|
}).passthrough().optional(),
|
|
8330
8468
|
autoApprove: union([boolean2(), array(nonBlank)]).optional(),
|
|
8331
8469
|
defaultEndpoint: unknown().optional(),
|
|
8332
|
-
verification: _enum(["self", "independent"]).optional(),
|
|
8470
|
+
verification: _enum(["proportional", "self", "independent"]).optional(),
|
|
8333
8471
|
delegation: unknown().optional(),
|
|
8334
8472
|
trailers: boolean2().optional(),
|
|
8335
8473
|
liveSessionGuard: boolean2().optional(),
|
|
@@ -12032,14 +12170,757 @@ class TaskStore {
|
|
|
12032
12170
|
}
|
|
12033
12171
|
|
|
12034
12172
|
// packages/workit-core/src/core/policy/derive.ts
|
|
12035
|
-
import { existsSync as
|
|
12036
|
-
import
|
|
12173
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
12174
|
+
import path15 from "node:path";
|
|
12037
12175
|
|
|
12038
12176
|
// packages/workit-core/src/ledger.ts
|
|
12039
12177
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12040
12178
|
import { createHash as createHash10, randomBytes as randomBytes3 } from "node:crypto";
|
|
12041
12179
|
import fs11 from "node:fs";
|
|
12180
|
+
import path14 from "node:path";
|
|
12181
|
+
|
|
12182
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
12183
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
12042
12184
|
import path13 from "node:path";
|
|
12185
|
+
|
|
12186
|
+
// packages/workit-core/src/git/default-base.ts
|
|
12187
|
+
var safeRef = (value) => value.length > 0 && !value.startsWith("-");
|
|
12188
|
+
var trackTrunk = (cwd) => {
|
|
12189
|
+
try {
|
|
12190
|
+
const resolved = vcsConfig("resolve", cwd);
|
|
12191
|
+
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
12192
|
+
return null;
|
|
12193
|
+
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
12194
|
+
return target && safeRef(target) ? target : null;
|
|
12195
|
+
} catch {
|
|
12196
|
+
return null;
|
|
12197
|
+
}
|
|
12198
|
+
};
|
|
12199
|
+
function defaultBase(cwd) {
|
|
12200
|
+
const trunk = trackTrunk(cwd);
|
|
12201
|
+
if (trunk) {
|
|
12202
|
+
for (const candidate of [`origin/${trunk}`, trunk])
|
|
12203
|
+
if (resolveRef(cwd, candidate))
|
|
12204
|
+
return candidate;
|
|
12205
|
+
}
|
|
12206
|
+
const originHead = originHeadRef(cwd);
|
|
12207
|
+
if (originHead && resolveRef(cwd, originHead))
|
|
12208
|
+
return originHead;
|
|
12209
|
+
for (const candidate of [
|
|
12210
|
+
"origin/main",
|
|
12211
|
+
"origin/master",
|
|
12212
|
+
"main",
|
|
12213
|
+
"master",
|
|
12214
|
+
"origin/develop",
|
|
12215
|
+
"develop"
|
|
12216
|
+
])
|
|
12217
|
+
if (resolveRef(cwd, candidate))
|
|
12218
|
+
return candidate;
|
|
12219
|
+
return null;
|
|
12220
|
+
}
|
|
12221
|
+
|
|
12222
|
+
// packages/workit-core/src/core/policy/attributes.ts
|
|
12223
|
+
function patternRegex(dir, pattern) {
|
|
12224
|
+
if (!pattern || pattern.endsWith("/") || pattern.startsWith('"') || pattern.startsWith("!"))
|
|
12225
|
+
return null;
|
|
12226
|
+
const anchored = pattern.includes("/");
|
|
12227
|
+
const body = pattern.replace(/^\//u, "");
|
|
12228
|
+
let source = "";
|
|
12229
|
+
for (let index = 0;index < body.length; index += 1) {
|
|
12230
|
+
const char = body[index];
|
|
12231
|
+
if (char === "*" && body[index + 1] === "*") {
|
|
12232
|
+
const slash = body[index + 2] === "/";
|
|
12233
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
12234
|
+
index += slash ? 2 : 1;
|
|
12235
|
+
} else if (char === "*")
|
|
12236
|
+
source += "[^/]*";
|
|
12237
|
+
else if (char === "?")
|
|
12238
|
+
source += "[^/]";
|
|
12239
|
+
else if (char === "[") {
|
|
12240
|
+
const end = body.indexOf("]", index + 2);
|
|
12241
|
+
if (end < 0)
|
|
12242
|
+
return null;
|
|
12243
|
+
const set = body.slice(index + 1, end).replace(/^!/u, "^").replace(/\\/gu, "\\\\");
|
|
12244
|
+
source += `[${set}]`;
|
|
12245
|
+
index = end;
|
|
12246
|
+
} else if (char === "\\" && index + 1 < body.length) {
|
|
12247
|
+
source += body[index + 1].replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
12248
|
+
index += 1;
|
|
12249
|
+
} else
|
|
12250
|
+
source += char.replace(/[.*+?^${}()|[\]\\/]/gu, "\\$&");
|
|
12251
|
+
}
|
|
12252
|
+
const prefix = dir ? `${dir.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}/` : "";
|
|
12253
|
+
try {
|
|
12254
|
+
return new RegExp(anchored ? `^${prefix}${source}$` : `^${prefix}(?:.*/)?${source}$`, "u");
|
|
12255
|
+
} catch {
|
|
12256
|
+
return null;
|
|
12257
|
+
}
|
|
12258
|
+
}
|
|
12259
|
+
function rulesOf(dir, text) {
|
|
12260
|
+
const out = [];
|
|
12261
|
+
for (const raw of text.split(`
|
|
12262
|
+
`)) {
|
|
12263
|
+
const line = raw.trim();
|
|
12264
|
+
if (!line || line.startsWith("#") || line.startsWith("[attr]"))
|
|
12265
|
+
continue;
|
|
12266
|
+
const [pattern, ...attributes] = line.split(/\s+/u);
|
|
12267
|
+
let generated = null;
|
|
12268
|
+
for (const attribute of attributes) {
|
|
12269
|
+
if (attribute === "linguist-generated" || attribute === "linguist-generated=true")
|
|
12270
|
+
generated = true;
|
|
12271
|
+
else if (attribute === "-linguist-generated" || attribute === "!linguist-generated" || attribute.startsWith("linguist-generated="))
|
|
12272
|
+
generated = false;
|
|
12273
|
+
}
|
|
12274
|
+
if (generated === null)
|
|
12275
|
+
continue;
|
|
12276
|
+
const regex = patternRegex(dir, pattern);
|
|
12277
|
+
if (regex)
|
|
12278
|
+
out.push({ regex, generated });
|
|
12279
|
+
}
|
|
12280
|
+
return out;
|
|
12281
|
+
}
|
|
12282
|
+
function generatedByAttributes(files, paths) {
|
|
12283
|
+
const rules = [...files].map(([file, text]) => {
|
|
12284
|
+
const dir = file.includes("/") ? file.slice(0, file.lastIndexOf("/")) : "";
|
|
12285
|
+
return {
|
|
12286
|
+
depth: dir ? dir.split("/").length : 0,
|
|
12287
|
+
rules: rulesOf(dir, text)
|
|
12288
|
+
};
|
|
12289
|
+
}).toSorted((left, right) => left.depth - right.depth).flatMap((entry) => entry.rules);
|
|
12290
|
+
const out = new Set;
|
|
12291
|
+
for (const file of paths) {
|
|
12292
|
+
const last = rules.findLast((rule) => rule.regex.test(file));
|
|
12293
|
+
if (last?.generated)
|
|
12294
|
+
out.add(file);
|
|
12295
|
+
}
|
|
12296
|
+
return out;
|
|
12297
|
+
}
|
|
12298
|
+
|
|
12299
|
+
// packages/workit-core/src/core/policy/substance.ts
|
|
12300
|
+
var SLASH_BLOCK = [["/*", "*/"]];
|
|
12301
|
+
var C_LIKE = { line: ["//"], block: SLASH_BLOCK, quotes: `"'` };
|
|
12302
|
+
var SYNTAXES = Object.fromEntries([
|
|
12303
|
+
...["ts", "mts", "cts", "js", "mjs", "cjs"].map((ext) => [ext, { ...C_LIKE, js: true }]),
|
|
12304
|
+
...["java", "kt", "kts", "scala", "swift", "dart", "groovy"].map((ext) => [
|
|
12305
|
+
ext,
|
|
12306
|
+
{ ...C_LIKE, triple: true }
|
|
12307
|
+
]),
|
|
12308
|
+
["go", { ...C_LIKE, rawBacktick: true }],
|
|
12309
|
+
...["css", "scss", "less"].map((ext) => [
|
|
12310
|
+
ext,
|
|
12311
|
+
{ line: [], block: SLASH_BLOCK, quotes: `"'`, cssUrl: true }
|
|
12312
|
+
]),
|
|
12313
|
+
...["py", "pyi"].map((ext) => [
|
|
12314
|
+
ext,
|
|
12315
|
+
{ line: ["#"], block: [], quotes: `"'`, triple: true, indented: true, fstring: true }
|
|
12316
|
+
]),
|
|
12317
|
+
["toml", { line: ["#"], block: [], quotes: `"`, literal: "'", triple: true }],
|
|
12318
|
+
...["html", "htm", "xml", "svg", "xhtml"].map((ext) => [
|
|
12319
|
+
ext,
|
|
12320
|
+
{ line: [], block: [["<!--", "-->"]], quotes: "", indented: true }
|
|
12321
|
+
])
|
|
12322
|
+
]);
|
|
12323
|
+
var DIRECTIVE = /@ts-|eslint|prettier|biome|oxlint|istanbul|\bc8\b|\bv8\b|@jsx|@flow|@vite|webpack|__PURE__|__NO_SIDE_EFFECTS__|source(?:Mapping)?URL|^\/\s*<|go:|\+build|nolint|noqa|type:|pyright|mypy|pylint|pragma|coding[:=]|fmt:|NOSONAR|@license|@preserve|@refresh|environment|language=|cython|ruff|isort|deno-|@vitest|@jest|swiftlint|ktlint|checkstyle|suppress|nullable|#(?:end)?region/iu;
|
|
12324
|
+
var extensionOf = (file) => {
|
|
12325
|
+
const name = file.slice(file.lastIndexOf("/") + 1);
|
|
12326
|
+
const dot = name.lastIndexOf(".");
|
|
12327
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : "";
|
|
12328
|
+
};
|
|
12329
|
+
var CI_PATH = /^\.github\/|(?:^|\/)(?:\.gitlab-ci\.ya?ml|Jenkinsfile|azure-pipelines\.ya?ml|\.pre-commit-config\.ya?ml|\.circleci\/.*)$/u;
|
|
12330
|
+
var syntaxOf = (file) => CI_PATH.test(file) ? null : SYNTAXES[extensionOf(file)] ?? null;
|
|
12331
|
+
var SPACE = /[ \t\r\f\v]/u;
|
|
12332
|
+
var WORD = /[\w$]/u;
|
|
12333
|
+
var NO_SPACE_AFTER = "([{,;";
|
|
12334
|
+
var NO_SPACE_BEFORE = ")]},;";
|
|
12335
|
+
var REGEX_AFTER = "(,=:[!&|?{};+-*%<>~^";
|
|
12336
|
+
var REGEX_KEYWORDS = new Set([
|
|
12337
|
+
"return",
|
|
12338
|
+
"typeof",
|
|
12339
|
+
"instanceof",
|
|
12340
|
+
"case",
|
|
12341
|
+
"do",
|
|
12342
|
+
"else",
|
|
12343
|
+
"in",
|
|
12344
|
+
"of",
|
|
12345
|
+
"new",
|
|
12346
|
+
"delete",
|
|
12347
|
+
"void",
|
|
12348
|
+
"throw",
|
|
12349
|
+
"yield",
|
|
12350
|
+
"await"
|
|
12351
|
+
]);
|
|
12352
|
+
function substanceOf(file, text) {
|
|
12353
|
+
const syntax = syntaxOf(file);
|
|
12354
|
+
if (!syntax)
|
|
12355
|
+
return plainSubstance(text);
|
|
12356
|
+
const markup = syntax.block.some(([open]) => open === "<!--");
|
|
12357
|
+
if (markup && (/<(?:script|style)\b|<!\[CDATA\[|<\?/iu.test(text) || /[^>\s][ \t]*<!--/u.test(text)))
|
|
12358
|
+
return plainSubstance(text);
|
|
12359
|
+
return lex(syntax, text);
|
|
12360
|
+
}
|
|
12361
|
+
function plainSubstance(text) {
|
|
12362
|
+
return text.split(`
|
|
12363
|
+
`).map((line) => line.replace(/[ \t\r\f\v]+$/u, "")).filter(Boolean).join(`
|
|
12364
|
+
`);
|
|
12365
|
+
}
|
|
12366
|
+
function lex(syntax, text) {
|
|
12367
|
+
const collapse = !syntax.indented;
|
|
12368
|
+
let out = "";
|
|
12369
|
+
let lineStart = 0;
|
|
12370
|
+
let kept = 0;
|
|
12371
|
+
let pendingSpace = false;
|
|
12372
|
+
const frames = [{ kind: "code", braces: 0 }];
|
|
12373
|
+
let index = 0;
|
|
12374
|
+
if (text.startsWith("#!")) {
|
|
12375
|
+
const end = text.indexOf(`
|
|
12376
|
+
`);
|
|
12377
|
+
out = end < 0 ? text : text.slice(0, end);
|
|
12378
|
+
kept = out.length;
|
|
12379
|
+
index = end < 0 ? text.length : end;
|
|
12380
|
+
}
|
|
12381
|
+
const emitCode = (chunk) => {
|
|
12382
|
+
if (collapse && pendingSpace && out.length > lineStart) {
|
|
12383
|
+
const before = out[out.length - 1];
|
|
12384
|
+
if (!NO_SPACE_AFTER.includes(before) && !NO_SPACE_BEFORE.includes(chunk[0]))
|
|
12385
|
+
out += " ";
|
|
12386
|
+
}
|
|
12387
|
+
pendingSpace = false;
|
|
12388
|
+
out += chunk;
|
|
12389
|
+
};
|
|
12390
|
+
const emitVerbatim = (chunk) => {
|
|
12391
|
+
emitCode(chunk);
|
|
12392
|
+
kept = out.length;
|
|
12393
|
+
};
|
|
12394
|
+
const newline = () => {
|
|
12395
|
+
if (!collapse) {
|
|
12396
|
+
let end = out.length;
|
|
12397
|
+
while (end > Math.max(lineStart, kept) && SPACE.test(out[end - 1]))
|
|
12398
|
+
end -= 1;
|
|
12399
|
+
out = out.slice(0, end);
|
|
12400
|
+
}
|
|
12401
|
+
pendingSpace = false;
|
|
12402
|
+
if (out.length > lineStart) {
|
|
12403
|
+
out += `
|
|
12404
|
+
`;
|
|
12405
|
+
lineStart = out.length;
|
|
12406
|
+
}
|
|
12407
|
+
};
|
|
12408
|
+
const regexAllowed = () => {
|
|
12409
|
+
let end = out.length;
|
|
12410
|
+
while (end > 0 && /\s/u.test(out[end - 1]))
|
|
12411
|
+
end -= 1;
|
|
12412
|
+
if (end === 0 || REGEX_AFTER.includes(out[end - 1]))
|
|
12413
|
+
return true;
|
|
12414
|
+
let start = end;
|
|
12415
|
+
while (start > 0 && WORD.test(out[start - 1]))
|
|
12416
|
+
start -= 1;
|
|
12417
|
+
return REGEX_KEYWORDS.has(out.slice(start, end));
|
|
12418
|
+
};
|
|
12419
|
+
const scanString = (quote, escapes, multiline) => {
|
|
12420
|
+
const prefix = text.slice(Math.max(0, index - 2), index).match(/[a-z]*$/iu)?.[0] ?? "";
|
|
12421
|
+
const fstring = syntax.fstring === true && /f/iu.test(prefix);
|
|
12422
|
+
let end = index + quote.length;
|
|
12423
|
+
while (end < text.length) {
|
|
12424
|
+
const char = text[end];
|
|
12425
|
+
if (escapes && char === "\\") {
|
|
12426
|
+
end += 2;
|
|
12427
|
+
continue;
|
|
12428
|
+
}
|
|
12429
|
+
if (text.startsWith(quote, end)) {
|
|
12430
|
+
if (fstring && unbalanced(text.slice(index + quote.length, end)))
|
|
12431
|
+
return false;
|
|
12432
|
+
emitVerbatim(text.slice(index, end + quote.length));
|
|
12433
|
+
index = end + quote.length;
|
|
12434
|
+
return true;
|
|
12435
|
+
}
|
|
12436
|
+
if (char === `
|
|
12437
|
+
` && !multiline)
|
|
12438
|
+
return false;
|
|
12439
|
+
end += 1;
|
|
12440
|
+
}
|
|
12441
|
+
return false;
|
|
12442
|
+
};
|
|
12443
|
+
while (index < text.length) {
|
|
12444
|
+
const frame = frames[frames.length - 1];
|
|
12445
|
+
const char = text[index];
|
|
12446
|
+
if (frame.kind === "template") {
|
|
12447
|
+
if (char === "\\") {
|
|
12448
|
+
emitVerbatim(text.slice(index, index + 2));
|
|
12449
|
+
index += 2;
|
|
12450
|
+
} else if (char === "`") {
|
|
12451
|
+
emitVerbatim(char);
|
|
12452
|
+
frames.pop();
|
|
12453
|
+
index += 1;
|
|
12454
|
+
} else if (text.startsWith("${", index)) {
|
|
12455
|
+
emitVerbatim("${");
|
|
12456
|
+
frames.push({ kind: "code", braces: 0 });
|
|
12457
|
+
index += 2;
|
|
12458
|
+
} else {
|
|
12459
|
+
out += char;
|
|
12460
|
+
kept = out.length;
|
|
12461
|
+
index += 1;
|
|
12462
|
+
}
|
|
12463
|
+
continue;
|
|
12464
|
+
}
|
|
12465
|
+
if (char === `
|
|
12466
|
+
`) {
|
|
12467
|
+
newline();
|
|
12468
|
+
index += 1;
|
|
12469
|
+
continue;
|
|
12470
|
+
}
|
|
12471
|
+
if (SPACE.test(char)) {
|
|
12472
|
+
if (collapse)
|
|
12473
|
+
pendingSpace = true;
|
|
12474
|
+
else
|
|
12475
|
+
out += char;
|
|
12476
|
+
index += 1;
|
|
12477
|
+
continue;
|
|
12478
|
+
}
|
|
12479
|
+
const lineOpener = syntax.line.find((opener) => text.startsWith(opener, index));
|
|
12480
|
+
if (lineOpener) {
|
|
12481
|
+
const end = text.indexOf(`
|
|
12482
|
+
`, index);
|
|
12483
|
+
const comment = text.slice(index, end < 0 ? text.length : end);
|
|
12484
|
+
if (DIRECTIVE.test(comment.slice(lineOpener.length)))
|
|
12485
|
+
emitVerbatim(comment);
|
|
12486
|
+
else
|
|
12487
|
+
pendingSpace = true;
|
|
12488
|
+
index = end < 0 ? text.length : end;
|
|
12489
|
+
continue;
|
|
12490
|
+
}
|
|
12491
|
+
const block = syntax.block.find(([open]) => text.startsWith(open, index));
|
|
12492
|
+
if (block) {
|
|
12493
|
+
const end = text.indexOf(block[1], index + block[0].length);
|
|
12494
|
+
if (end < 0)
|
|
12495
|
+
return null;
|
|
12496
|
+
const comment = text.slice(index, end + block[1].length);
|
|
12497
|
+
if (DIRECTIVE.test(comment.slice(block[0].length, -block[1].length)))
|
|
12498
|
+
emitVerbatim(comment);
|
|
12499
|
+
else
|
|
12500
|
+
pendingSpace = true;
|
|
12501
|
+
if (comment.includes(`
|
|
12502
|
+
`))
|
|
12503
|
+
newline();
|
|
12504
|
+
index = end + block[1].length;
|
|
12505
|
+
continue;
|
|
12506
|
+
}
|
|
12507
|
+
if (syntax.triple && (char === '"' || char === "'") && text.startsWith(char.repeat(3), index)) {
|
|
12508
|
+
if (!scanString(char.repeat(3), !syntax.literal?.includes(char), true))
|
|
12509
|
+
return null;
|
|
12510
|
+
continue;
|
|
12511
|
+
}
|
|
12512
|
+
if (syntax.quotes.includes(char)) {
|
|
12513
|
+
if (!scanString(char, true, Boolean(syntax.multiline)))
|
|
12514
|
+
return null;
|
|
12515
|
+
continue;
|
|
12516
|
+
}
|
|
12517
|
+
if (syntax.literal?.includes(char)) {
|
|
12518
|
+
if (!scanString(char, false, false))
|
|
12519
|
+
return null;
|
|
12520
|
+
continue;
|
|
12521
|
+
}
|
|
12522
|
+
if (syntax.rawBacktick && char === "`") {
|
|
12523
|
+
if (!scanString("`", false, true))
|
|
12524
|
+
return null;
|
|
12525
|
+
continue;
|
|
12526
|
+
}
|
|
12527
|
+
if (syntax.js && char === "`") {
|
|
12528
|
+
emitVerbatim(char);
|
|
12529
|
+
frames.push({ kind: "template" });
|
|
12530
|
+
index += 1;
|
|
12531
|
+
continue;
|
|
12532
|
+
}
|
|
12533
|
+
if (syntax.cssUrl && /^url\(\s*[^"')\s]/iu.test(text.slice(index, index + 64)))
|
|
12534
|
+
return null;
|
|
12535
|
+
if (syntax.js && char === "/" && !regexAllowed() && out.trimEnd().endsWith(")")) {
|
|
12536
|
+
const lineEnd = text.indexOf(`
|
|
12537
|
+
`, index);
|
|
12538
|
+
if (text.slice(index + 1, lineEnd < 0 ? text.length : lineEnd).includes("/"))
|
|
12539
|
+
return null;
|
|
12540
|
+
}
|
|
12541
|
+
if (syntax.js && char === "/" && regexAllowed()) {
|
|
12542
|
+
let end = index + 1;
|
|
12543
|
+
let inClass = false;
|
|
12544
|
+
while (end < text.length && (inClass || text[end] !== "/")) {
|
|
12545
|
+
if (text[end] === `
|
|
12546
|
+
`)
|
|
12547
|
+
return null;
|
|
12548
|
+
if (text[end] === "\\")
|
|
12549
|
+
end += 1;
|
|
12550
|
+
else if (text[end] === "[")
|
|
12551
|
+
inClass = true;
|
|
12552
|
+
else if (text[end] === "]")
|
|
12553
|
+
inClass = false;
|
|
12554
|
+
end += 1;
|
|
12555
|
+
}
|
|
12556
|
+
if (end >= text.length)
|
|
12557
|
+
return null;
|
|
12558
|
+
emitVerbatim(text.slice(index, end + 1));
|
|
12559
|
+
index = end + 1;
|
|
12560
|
+
continue;
|
|
12561
|
+
}
|
|
12562
|
+
if (frames.length > 1 && frame.kind === "code") {
|
|
12563
|
+
if (char === "{")
|
|
12564
|
+
frame.braces += 1;
|
|
12565
|
+
else if (char === "}") {
|
|
12566
|
+
if (frame.braces === 0) {
|
|
12567
|
+
emitVerbatim(char);
|
|
12568
|
+
frames.pop();
|
|
12569
|
+
index += 1;
|
|
12570
|
+
continue;
|
|
12571
|
+
}
|
|
12572
|
+
frame.braces -= 1;
|
|
12573
|
+
}
|
|
12574
|
+
}
|
|
12575
|
+
emitCode(char);
|
|
12576
|
+
index += 1;
|
|
12577
|
+
}
|
|
12578
|
+
if (frames.length !== 1)
|
|
12579
|
+
return null;
|
|
12580
|
+
newline();
|
|
12581
|
+
return out;
|
|
12582
|
+
}
|
|
12583
|
+
function unbalanced(body) {
|
|
12584
|
+
let depth = 0;
|
|
12585
|
+
for (const part of body.replaceAll("{{", "").replaceAll("}}", "")) {
|
|
12586
|
+
if (part === "{")
|
|
12587
|
+
depth += 1;
|
|
12588
|
+
else if (part === "}")
|
|
12589
|
+
depth = Math.max(0, depth - 1);
|
|
12590
|
+
}
|
|
12591
|
+
return depth > 0;
|
|
12592
|
+
}
|
|
12593
|
+
function hasOpaqueLineBreak(text) {
|
|
12594
|
+
return /\r(?!\n)|[\u2028\u2029]/u.test(text);
|
|
12595
|
+
}
|
|
12596
|
+
var decoder = new TextDecoder("utf-8", { fatal: true });
|
|
12597
|
+
function textOf(bytes) {
|
|
12598
|
+
if (bytes.includes(0))
|
|
12599
|
+
return null;
|
|
12600
|
+
try {
|
|
12601
|
+
return decoder.decode(bytes);
|
|
12602
|
+
} catch {
|
|
12603
|
+
return null;
|
|
12604
|
+
}
|
|
12605
|
+
}
|
|
12606
|
+
function cosmeticChange(file, before, after) {
|
|
12607
|
+
const left = textOf(before);
|
|
12608
|
+
const right = textOf(after);
|
|
12609
|
+
if (left === null || right === null)
|
|
12610
|
+
return false;
|
|
12611
|
+
if (hasOpaqueLineBreak(left) || hasOpaqueLineBreak(right))
|
|
12612
|
+
return false;
|
|
12613
|
+
const a = substanceOf(file, left);
|
|
12614
|
+
return a !== null && a === substanceOf(file, right);
|
|
12615
|
+
}
|
|
12616
|
+
|
|
12617
|
+
// packages/workit-core/src/core/policy/lane.ts
|
|
12618
|
+
var LANE_THRESHOLDS = {
|
|
12619
|
+
selfMaxLines: 100,
|
|
12620
|
+
selfMaxFiles: 5,
|
|
12621
|
+
fullMinLines: 500
|
|
12622
|
+
};
|
|
12623
|
+
var RISK_TOKENS = new Set([
|
|
12624
|
+
"auth",
|
|
12625
|
+
"authn",
|
|
12626
|
+
"authz",
|
|
12627
|
+
"oauth",
|
|
12628
|
+
"security",
|
|
12629
|
+
"permission",
|
|
12630
|
+
"permissions",
|
|
12631
|
+
"secret",
|
|
12632
|
+
"secrets",
|
|
12633
|
+
"crypto",
|
|
12634
|
+
"gate",
|
|
12635
|
+
"gates",
|
|
12636
|
+
"policy",
|
|
12637
|
+
"policies",
|
|
12638
|
+
"hook",
|
|
12639
|
+
"hooks",
|
|
12640
|
+
"husky",
|
|
12641
|
+
"lefthook",
|
|
12642
|
+
"release",
|
|
12643
|
+
"releases",
|
|
12644
|
+
"migration",
|
|
12645
|
+
"migrations",
|
|
12646
|
+
"migrate",
|
|
12647
|
+
"workflows"
|
|
12648
|
+
]);
|
|
12649
|
+
var RISK_FILES = new Set([
|
|
12650
|
+
".gitlab-ci.yml",
|
|
12651
|
+
"Jenkinsfile",
|
|
12652
|
+
"azure-pipelines.yml",
|
|
12653
|
+
".pre-commit-config.yaml",
|
|
12654
|
+
".workit/risk-paths"
|
|
12655
|
+
]);
|
|
12656
|
+
var RISK_NAMES = new Set([".gitattributes"]);
|
|
12657
|
+
var RISK_PATHS_FILE = ".workit/risk-paths";
|
|
12658
|
+
var LOCKFILES = new Set([
|
|
12659
|
+
"bun.lock",
|
|
12660
|
+
"bun.lockb",
|
|
12661
|
+
"package-lock.json",
|
|
12662
|
+
"npm-shrinkwrap.json",
|
|
12663
|
+
"yarn.lock",
|
|
12664
|
+
"pnpm-lock.yaml",
|
|
12665
|
+
"Cargo.lock",
|
|
12666
|
+
"go.sum",
|
|
12667
|
+
"poetry.lock",
|
|
12668
|
+
"uv.lock",
|
|
12669
|
+
"Pipfile.lock",
|
|
12670
|
+
"Gemfile.lock",
|
|
12671
|
+
"composer.lock"
|
|
12672
|
+
]);
|
|
12673
|
+
var DOC_EXT = /\.(?:md|mdx|markdown|rst|adoc)$/iu;
|
|
12674
|
+
var DOC_NAMES = /^(?:license|licence|notice|authors|changelog|changes|contributors)$/iu;
|
|
12675
|
+
var INSTRUCTIONS = /(?:^|\/)(?:AGENTS|CLAUDE|GEMINI|SKILL)\.md$|(?:^|\/)(?:skills|agents|commands|rules|prompts)\/[^/]+/iu;
|
|
12676
|
+
var TEST_PATH = /(?:^|\/)(?:test|tests|__tests__)\/|\.(?:test|spec)\.[^/]+$|_test\.[^/]+$/iu;
|
|
12677
|
+
var basename = (file) => file.slice(file.lastIndexOf("/") + 1);
|
|
12678
|
+
var isDoc = (file) => !INSTRUCTIONS.test(file) && (DOC_EXT.test(file) || DOC_NAMES.test(basename(file)));
|
|
12679
|
+
function kindOf(file) {
|
|
12680
|
+
if (file.binary)
|
|
12681
|
+
return "behavior";
|
|
12682
|
+
if (file.generated)
|
|
12683
|
+
return "generated";
|
|
12684
|
+
if (LOCKFILES.has(basename(file.path)))
|
|
12685
|
+
return "lockfile";
|
|
12686
|
+
if (isDoc(file.path))
|
|
12687
|
+
return "doc";
|
|
12688
|
+
if (file.cosmetic)
|
|
12689
|
+
return "cosmetic";
|
|
12690
|
+
return TEST_PATH.test(file.path) ? "test" : "behavior";
|
|
12691
|
+
}
|
|
12692
|
+
var globCache = new Map;
|
|
12693
|
+
function globMatch(pattern, file) {
|
|
12694
|
+
let regex = globCache.get(pattern);
|
|
12695
|
+
if (!regex) {
|
|
12696
|
+
let source = "";
|
|
12697
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
12698
|
+
const char = pattern[index];
|
|
12699
|
+
if (char === "*" && pattern[index + 1] === "*") {
|
|
12700
|
+
source += pattern[index + 2] === "/" ? "(?:[^/]*/)*" : ".*";
|
|
12701
|
+
index += pattern[index + 2] === "/" ? 2 : 1;
|
|
12702
|
+
} else if (char === "*")
|
|
12703
|
+
source += "[^/]*";
|
|
12704
|
+
else if (char === "?")
|
|
12705
|
+
source += "[^/]";
|
|
12706
|
+
else
|
|
12707
|
+
source += char.replace(/[.+^${}()|[\]\\]/gu, "\\$&");
|
|
12708
|
+
}
|
|
12709
|
+
regex = new RegExp(`^${source}$`, "u");
|
|
12710
|
+
globCache.set(pattern, regex);
|
|
12711
|
+
}
|
|
12712
|
+
return regex.test(file) || file.startsWith(`${pattern.replace(/\/$/u, "")}/`);
|
|
12713
|
+
}
|
|
12714
|
+
function isRiskPath(file, extra = []) {
|
|
12715
|
+
if (extra.some((pattern) => globMatch(pattern, file)))
|
|
12716
|
+
return true;
|
|
12717
|
+
if (RISK_FILES.has(file) || RISK_NAMES.has(basename(file)))
|
|
12718
|
+
return true;
|
|
12719
|
+
if (TEST_PATH.test(file) || isDoc(file))
|
|
12720
|
+
return false;
|
|
12721
|
+
return file.split("/").map((segment, index, all) => index === all.length - 1 ? segment.replace(/\.[^.]+$/u, "") : segment).some((segment) => segment.toLowerCase().split(/[-_.]/u).some((token) => RISK_TOKENS.has(token)));
|
|
12722
|
+
}
|
|
12723
|
+
var parseRiskPaths = (text) => text.split(`
|
|
12724
|
+
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\//u, ""));
|
|
12725
|
+
function computeLane(input) {
|
|
12726
|
+
const riskFiles = input.files.filter((file) => isRiskPath(file.path, input.riskPaths)).map((file) => file.path);
|
|
12727
|
+
const risky = new Set(riskFiles);
|
|
12728
|
+
const kinds = input.files.map((file) => ({
|
|
12729
|
+
file,
|
|
12730
|
+
kind: risky.has(file.path) ? "behavior" : kindOf(file)
|
|
12731
|
+
}));
|
|
12732
|
+
const behavior = kinds.filter((entry) => entry.kind === "behavior" || entry.kind === "test" || entry.kind === "lockfile");
|
|
12733
|
+
const lines = behavior.filter((entry) => entry.kind !== "lockfile").reduce((sum, entry) => sum + entry.file.added + entry.file.deleted, 0);
|
|
12734
|
+
const opaque = behavior.filter((entry) => entry.kind === "lockfile" || entry.file.binary);
|
|
12735
|
+
const result = (lane, reasons) => ({
|
|
12736
|
+
lane,
|
|
12737
|
+
reasons,
|
|
12738
|
+
lines,
|
|
12739
|
+
files: behavior.length,
|
|
12740
|
+
riskFiles
|
|
12741
|
+
});
|
|
12742
|
+
const { selfMaxLines, selfMaxFiles, fullMinLines } = LANE_THRESHOLDS;
|
|
12743
|
+
if (input.riskTier === "high")
|
|
12744
|
+
return result("full", ["the task judgment records high risk"]);
|
|
12745
|
+
if (riskFiles.length)
|
|
12746
|
+
return result("full", [
|
|
12747
|
+
`touches a risk path: ${riskFiles.slice(0, 3).join(", ")}${riskFiles.length > 3 ? ", …" : ""}`
|
|
12748
|
+
]);
|
|
12749
|
+
if (!behavior.length)
|
|
12750
|
+
return result("none", [
|
|
12751
|
+
input.files.length ? "only docs, comments, formatting or generated copies changed" : "no changes"
|
|
12752
|
+
]);
|
|
12753
|
+
if (lines >= fullMinLines)
|
|
12754
|
+
return result("full", [`large diff: ${lines} lines (≥ ${fullMinLines})`]);
|
|
12755
|
+
if (lines > selfMaxLines)
|
|
12756
|
+
return result("focused", [`${lines} lines changed (> ${selfMaxLines})`]);
|
|
12757
|
+
if (behavior.length > selfMaxFiles)
|
|
12758
|
+
return result("focused", [`${behavior.length} behavior files changed (> ${selfMaxFiles})`]);
|
|
12759
|
+
if (opaque.length)
|
|
12760
|
+
return result("focused", [
|
|
12761
|
+
`a lockfile or binary changed: ${opaque.slice(0, 3).map((entry) => entry.file.path).join(", ")}${opaque.length > 3 ? ", …" : ""}`
|
|
12762
|
+
]);
|
|
12763
|
+
return result("self", [
|
|
12764
|
+
`small change: ${lines} line${lines === 1 ? "" : "s"} in ${behavior.length} file${behavior.length === 1 ? "" : "s"}, no risk path`
|
|
12765
|
+
]);
|
|
12766
|
+
}
|
|
12767
|
+
function untrackedStat(cwd, file) {
|
|
12768
|
+
try {
|
|
12769
|
+
const bytes = readFileSync4(path13.join(cwd, file));
|
|
12770
|
+
if (bytes.includes(0))
|
|
12771
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
12772
|
+
const text = bytes.toString("utf8");
|
|
12773
|
+
if (hasOpaqueLineBreak(text))
|
|
12774
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
12775
|
+
const lines = text.split(`
|
|
12776
|
+
`).length - (text.endsWith(`
|
|
12777
|
+
`) ? 1 : 0);
|
|
12778
|
+
return { path: file, added: lines, deleted: 0 };
|
|
12779
|
+
} catch {
|
|
12780
|
+
return { path: file, added: 0, deleted: 0, binary: true };
|
|
12781
|
+
}
|
|
12782
|
+
}
|
|
12783
|
+
var REGULAR = new Set(["100644", "100755"]);
|
|
12784
|
+
var ABSENT = /^0+$/u;
|
|
12785
|
+
function cosmeticFiles(cwd, fork, head) {
|
|
12786
|
+
const raw = diffRaw(cwd, fork, head);
|
|
12787
|
+
if (!raw)
|
|
12788
|
+
return null;
|
|
12789
|
+
const candidates = raw.filter((entry) => REGULAR.has(entry.oldMode) && entry.oldMode === entry.newMode && !ABSENT.test(entry.oldBlob) && (head === null || !ABSENT.test(entry.newBlob)));
|
|
12790
|
+
const blobs = readBlobs(cwd, candidates.flatMap((entry) => [entry.oldBlob, entry.newBlob]));
|
|
12791
|
+
if (!blobs)
|
|
12792
|
+
return null;
|
|
12793
|
+
const out = new Set;
|
|
12794
|
+
const opaque = new Set;
|
|
12795
|
+
for (const entry of candidates) {
|
|
12796
|
+
const before = blobs.get(entry.oldBlob);
|
|
12797
|
+
let after = blobs.get(entry.newBlob);
|
|
12798
|
+
if (!after && head === null && ABSENT.test(entry.newBlob))
|
|
12799
|
+
try {
|
|
12800
|
+
after = readFileSync4(path13.join(cwd, entry.path));
|
|
12801
|
+
} catch {
|
|
12802
|
+
after = undefined;
|
|
12803
|
+
}
|
|
12804
|
+
if (before && after && cosmeticChange(entry.path, before, after))
|
|
12805
|
+
out.add(entry.path);
|
|
12806
|
+
else if ([before, after].some((bytes) => bytes && hasOpaqueLineBreak(bytes.toString("utf8"))))
|
|
12807
|
+
opaque.add(entry.path);
|
|
12808
|
+
}
|
|
12809
|
+
return { cosmetic: out, opaque };
|
|
12810
|
+
}
|
|
12811
|
+
function generatedAt(cwd, fork, paths) {
|
|
12812
|
+
const files = treeFilesNamed(cwd, fork, ".gitattributes");
|
|
12813
|
+
const blobs = files?.size ? readBlobs(cwd, [...files.values()]) : null;
|
|
12814
|
+
if (!files || !blobs)
|
|
12815
|
+
return new Set;
|
|
12816
|
+
const texts = new Map;
|
|
12817
|
+
for (const [file, id] of files) {
|
|
12818
|
+
const bytes = blobs.get(id);
|
|
12819
|
+
if (bytes)
|
|
12820
|
+
texts.set(file, bytes.toString("utf8"));
|
|
12821
|
+
}
|
|
12822
|
+
return generatedByAttributes(texts, paths);
|
|
12823
|
+
}
|
|
12824
|
+
var forkOf = (cwd, base, head) => mergeBase(cwd, base, head ?? "HEAD");
|
|
12825
|
+
function laneFiles(cwd, fork, head, attributesAt = fork) {
|
|
12826
|
+
const stats = diffNumstat(cwd, fork, head);
|
|
12827
|
+
const cosmetic = stats ? cosmeticFiles(cwd, fork, head) : null;
|
|
12828
|
+
if (!stats || !cosmetic)
|
|
12829
|
+
return null;
|
|
12830
|
+
const files = [
|
|
12831
|
+
...stats.map((entry) => ({
|
|
12832
|
+
...entry,
|
|
12833
|
+
...cosmetic.opaque.has(entry.path) ? { binary: true } : {},
|
|
12834
|
+
cosmetic: !entry.binary && cosmetic.cosmetic.has(entry.path)
|
|
12835
|
+
})),
|
|
12836
|
+
...head === null ? untrackedFiles(cwd).map((file) => untrackedStat(cwd, file)) : []
|
|
12837
|
+
];
|
|
12838
|
+
const generated = generatedAt(cwd, attributesAt, files.map((entry) => entry.path));
|
|
12839
|
+
return files.map((entry) => ({
|
|
12840
|
+
...entry,
|
|
12841
|
+
generated: generated.has(entry.path)
|
|
12842
|
+
}));
|
|
12843
|
+
}
|
|
12844
|
+
function driftFiles(cwd, range) {
|
|
12845
|
+
if (range.fromFork === range.toFork)
|
|
12846
|
+
return laneFiles(cwd, range.from, range.to, range.toFork);
|
|
12847
|
+
const drifted = ownChangeDrift(cwd, range);
|
|
12848
|
+
const stats = drifted ? diffNumstat(cwd, range.from, range.to) : null;
|
|
12849
|
+
if (!drifted || !stats)
|
|
12850
|
+
return null;
|
|
12851
|
+
const byPath = new Map(stats.map((entry) => [entry.path, entry]));
|
|
12852
|
+
const generated = generatedAt(cwd, range.toFork, drifted);
|
|
12853
|
+
return drifted.map((file) => ({
|
|
12854
|
+
path: file,
|
|
12855
|
+
added: byPath.get(file)?.added ?? 0,
|
|
12856
|
+
deleted: byPath.get(file)?.deleted ?? 0,
|
|
12857
|
+
...byPath.get(file)?.binary ? { binary: true } : {},
|
|
12858
|
+
cosmetic: false,
|
|
12859
|
+
generated: generated.has(file)
|
|
12860
|
+
}));
|
|
12861
|
+
}
|
|
12862
|
+
function branchLane(cwd, options) {
|
|
12863
|
+
const candidates = options.base ? [`origin/${options.base}`, options.base] : [];
|
|
12864
|
+
const base = candidates.find((ref) => resolveRef(cwd, ref) !== null) ?? defaultBase(cwd) ?? null;
|
|
12865
|
+
const head = options.workingTree ? null : options.head;
|
|
12866
|
+
const fork = base && (options.workingTree || options.head) ? forkOf(cwd, base, head) : null;
|
|
12867
|
+
const files = fork ? laneFiles(cwd, fork, head) : null;
|
|
12868
|
+
const riskTier = options.judgment?.riskTier ?? null;
|
|
12869
|
+
if (!fork || !files) {
|
|
12870
|
+
const high = riskTier === "high";
|
|
12871
|
+
return {
|
|
12872
|
+
lane: high ? "full" : "focused",
|
|
12873
|
+
reasons: [
|
|
12874
|
+
`the diff of ${options.head?.slice(0, 12) ?? "(no head)"} against ${base ?? "(no base)"} cannot be read`,
|
|
12875
|
+
...high ? ["the task judgment records high risk"] : []
|
|
12876
|
+
],
|
|
12877
|
+
lines: 0,
|
|
12878
|
+
files: 0,
|
|
12879
|
+
riskFiles: [],
|
|
12880
|
+
base,
|
|
12881
|
+
head: options.head
|
|
12882
|
+
};
|
|
12883
|
+
}
|
|
12884
|
+
const repoRisk = parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? "");
|
|
12885
|
+
return {
|
|
12886
|
+
...computeLane({
|
|
12887
|
+
files,
|
|
12888
|
+
riskTier,
|
|
12889
|
+
riskPaths: [...repoRisk, ...options.riskPaths ?? []]
|
|
12890
|
+
}),
|
|
12891
|
+
base,
|
|
12892
|
+
head: options.head
|
|
12893
|
+
};
|
|
12894
|
+
}
|
|
12895
|
+
var laneNeed = (lane) => lane === "none" ? "none" : lane === "self" ? "self" : "non-author";
|
|
12896
|
+
|
|
12897
|
+
// packages/workit-core/src/core/policy/carry.ts
|
|
12898
|
+
var TEST_ONLY_KINDS = new Set(["doc", "cosmetic", "generated", "test"]);
|
|
12899
|
+
function verdictDrift(cwd, input) {
|
|
12900
|
+
const since = input.from;
|
|
12901
|
+
const stale = (reason, files = null, focused = false) => ({
|
|
12902
|
+
carries: false,
|
|
12903
|
+
since,
|
|
12904
|
+
files,
|
|
12905
|
+
focused,
|
|
12906
|
+
reason
|
|
12907
|
+
});
|
|
12908
|
+
const fromFork = mergeBase(cwd, input.fromBase, input.from);
|
|
12909
|
+
const toFork = mergeBase(cwd, input.toBase, input.to);
|
|
12910
|
+
const files = fromFork && toFork ? driftFiles(cwd, { from: input.from, to: input.to, fromFork, toFork }) : null;
|
|
12911
|
+
if (!fromFork || !toFork || !files)
|
|
12912
|
+
return stale(`the change since ${since.slice(0, 12)} cannot be read`);
|
|
12913
|
+
const riskPaths = [fromFork, toFork].flatMap((fork) => parseRiskPaths(fileAt(cwd, fork, RISK_PATHS_FILE) ?? ""));
|
|
12914
|
+
const lane = computeLane({ files, riskPaths });
|
|
12915
|
+
const paths = files.map((file) => file.path);
|
|
12916
|
+
if (lane.lane === "none")
|
|
12917
|
+
return { carries: true, rule: "lane-none", since, files: paths };
|
|
12918
|
+
if (input.kind === "unit" && lane.lane === "self" && files.every((file) => TEST_ONLY_KINDS.has(kindOf(file))))
|
|
12919
|
+
return { carries: true, rule: "tests-only", since, files: paths };
|
|
12920
|
+
return stale(lane.reasons[0] ?? `lane ${lane.lane}`, paths, lane.lane === "self");
|
|
12921
|
+
}
|
|
12922
|
+
|
|
12923
|
+
// packages/workit-core/src/ledger.ts
|
|
12043
12924
|
var LEDGER_VERSION = 1;
|
|
12044
12925
|
var MAX_LINE_BYTES = 4096;
|
|
12045
12926
|
var PASSING = new Set(["verified", "tests-verified", "type-check-only"]);
|
|
@@ -12084,52 +12965,19 @@ var git3 = (cwd, args) => {
|
|
|
12084
12965
|
const value = run.ok ? run.stdout.trim() : "";
|
|
12085
12966
|
return value ? value : null;
|
|
12086
12967
|
};
|
|
12087
|
-
var
|
|
12968
|
+
var safeRef2 = (value) => value.length > 0 && !value.startsWith("-");
|
|
12088
12969
|
function resolveCommit(cwd, ref) {
|
|
12089
|
-
if (!
|
|
12970
|
+
if (!safeRef2(ref))
|
|
12090
12971
|
return null;
|
|
12091
12972
|
return git3(cwd, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
12092
12973
|
}
|
|
12093
12974
|
function branchHead(cwd, branch) {
|
|
12094
|
-
if (!
|
|
12975
|
+
if (!safeRef2(branch))
|
|
12095
12976
|
return null;
|
|
12096
12977
|
return resolveCommit(cwd, `refs/heads/${branch}`) ?? resolveCommit(cwd, `refs/remotes/origin/${branch}`);
|
|
12097
12978
|
}
|
|
12098
|
-
var trackTrunk = (cwd) => {
|
|
12099
|
-
try {
|
|
12100
|
-
const resolved = vcsConfig("resolve", cwd);
|
|
12101
|
-
if (resolved.ok === false || !resolved.releaseTrack || resolved.releaseTrack.blocking)
|
|
12102
|
-
return null;
|
|
12103
|
-
const target = String(resolved.defaultTargetBranch ?? "").trim();
|
|
12104
|
-
return target && safeRef(target) ? target : null;
|
|
12105
|
-
} catch {
|
|
12106
|
-
return null;
|
|
12107
|
-
}
|
|
12108
|
-
};
|
|
12109
|
-
function defaultBase(cwd) {
|
|
12110
|
-
const trunk = trackTrunk(cwd);
|
|
12111
|
-
if (trunk) {
|
|
12112
|
-
for (const candidate of [`origin/${trunk}`, trunk])
|
|
12113
|
-
if (resolveCommit(cwd, candidate))
|
|
12114
|
-
return candidate;
|
|
12115
|
-
}
|
|
12116
|
-
const originHead = git3(cwd, ["symbolic-ref", "-q", "--short", "refs/remotes/origin/HEAD"]);
|
|
12117
|
-
if (originHead && resolveCommit(cwd, originHead))
|
|
12118
|
-
return originHead;
|
|
12119
|
-
for (const candidate of [
|
|
12120
|
-
"origin/main",
|
|
12121
|
-
"origin/master",
|
|
12122
|
-
"main",
|
|
12123
|
-
"master",
|
|
12124
|
-
"origin/develop",
|
|
12125
|
-
"develop"
|
|
12126
|
-
])
|
|
12127
|
-
if (resolveCommit(cwd, candidate))
|
|
12128
|
-
return candidate;
|
|
12129
|
-
return null;
|
|
12130
|
-
}
|
|
12131
12979
|
function diffHash(cwd, base, head) {
|
|
12132
|
-
if (!
|
|
12980
|
+
if (!safeRef2(base) || !safeRef2(head))
|
|
12133
12981
|
return null;
|
|
12134
12982
|
const diff = gitRun2(cwd, [
|
|
12135
12983
|
"diff",
|
|
@@ -12161,14 +13009,14 @@ function diffHash(cwd, base, head) {
|
|
|
12161
13009
|
return `sha256:${hash.digest("hex")}`;
|
|
12162
13010
|
}
|
|
12163
13011
|
function storeRoot(cwd) {
|
|
12164
|
-
const location = resolveStore(
|
|
13012
|
+
const location = resolveStore(path14.resolve(cwd));
|
|
12165
13013
|
if (location instanceof Error)
|
|
12166
13014
|
return err("unavailable", location.message.replace(/; fix the repository.*$/u, ""), "fix the repository (git status should work here) or run from a non-git directory");
|
|
12167
13015
|
return { ok: true, value: { root: location.dir, shared: location.shared } };
|
|
12168
13016
|
}
|
|
12169
13017
|
function ledgerPath(cwd) {
|
|
12170
13018
|
const root = storeRoot(cwd);
|
|
12171
|
-
return root.ok ? { ok: true, value:
|
|
13019
|
+
return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
12172
13020
|
}
|
|
12173
13021
|
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
12174
13022
|
var NETWORK_FS_TYPES = new Set([
|
|
@@ -12198,13 +13046,13 @@ var LOCK_STALE_MS = 30000;
|
|
|
12198
13046
|
var LOCK_OWNER = "owner";
|
|
12199
13047
|
var lockToken = (lock) => {
|
|
12200
13048
|
try {
|
|
12201
|
-
return fs11.readFileSync(
|
|
13049
|
+
return fs11.readFileSync(path14.join(lock, LOCK_OWNER), "utf8");
|
|
12202
13050
|
} catch {
|
|
12203
13051
|
return null;
|
|
12204
13052
|
}
|
|
12205
13053
|
};
|
|
12206
13054
|
var lockAgeMs = (lock) => {
|
|
12207
|
-
for (const target of [
|
|
13055
|
+
for (const target of [path14.join(lock, LOCK_OWNER), lock]) {
|
|
12208
13056
|
try {
|
|
12209
13057
|
return Date.now() - fs11.statSync(target).mtimeMs;
|
|
12210
13058
|
} catch {}
|
|
@@ -12221,7 +13069,7 @@ var ledgerLock = {
|
|
|
12221
13069
|
throw error;
|
|
12222
13070
|
}
|
|
12223
13071
|
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
12224
|
-
fs11.writeFileSync(
|
|
13072
|
+
fs11.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
12225
13073
|
return token;
|
|
12226
13074
|
},
|
|
12227
13075
|
release(lock, token) {
|
|
@@ -12354,8 +13202,8 @@ function appendLine(file, build, options) {
|
|
|
12354
13202
|
}
|
|
12355
13203
|
};
|
|
12356
13204
|
try {
|
|
12357
|
-
fs11.mkdirSync(
|
|
12358
|
-
if (!options.forceLock && !onNetworkFs(
|
|
13205
|
+
fs11.mkdirSync(path14.dirname(file), { recursive: true });
|
|
13206
|
+
if (!options.forceLock && !onNetworkFs(path14.dirname(file)))
|
|
12359
13207
|
return write();
|
|
12360
13208
|
const locked = withLedgerLock(file, write);
|
|
12361
13209
|
return locked ?? err("busy", `ledger lock ${file}.lock is held`, "retry; remove the .lock directory if no workit process is running");
|
|
@@ -12589,7 +13437,7 @@ function verdictBasis(verdict, current) {
|
|
|
12589
13437
|
}
|
|
12590
13438
|
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
12591
13439
|
function trailerSessions(cwd, base, head) {
|
|
12592
|
-
if (!base || !head || !
|
|
13440
|
+
if (!base || !head || !safeRef2(base) || !safeRef2(head))
|
|
12593
13441
|
return [];
|
|
12594
13442
|
const out = git3(cwd, [
|
|
12595
13443
|
"log",
|
|
@@ -12602,7 +13450,7 @@ var RENAMED = /^branch: renamed refs\/heads\/(.+) to refs\/heads\/(.+)$/iu;
|
|
|
12602
13450
|
function branchScope(cwd, branch) {
|
|
12603
13451
|
const renamedAt = new Map;
|
|
12604
13452
|
let since = Number.NEGATIVE_INFINITY;
|
|
12605
|
-
if (
|
|
13453
|
+
if (safeRef2(branch)) {
|
|
12606
13454
|
const log = git3(cwd, [
|
|
12607
13455
|
"log",
|
|
12608
13456
|
"-g",
|
|
@@ -12747,12 +13595,29 @@ function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
|
12747
13595
|
}
|
|
12748
13596
|
return effective;
|
|
12749
13597
|
}
|
|
12750
|
-
|
|
13598
|
+
var restackExact = new WeakMap;
|
|
13599
|
+
function restackCarries(exactIn, rows, branch, from, to) {
|
|
12751
13600
|
if (!from || !to)
|
|
12752
13601
|
return false;
|
|
13602
|
+
const exact = (row, fromHead, head) => {
|
|
13603
|
+
if (exactIn === null)
|
|
13604
|
+
return true;
|
|
13605
|
+
const cwd = exactIn;
|
|
13606
|
+
let known = restackExact.get(row);
|
|
13607
|
+
if (known === undefined) {
|
|
13608
|
+
known = typeof row.fromBase === "string" && typeof row.toBase === "string" && sameOwnChange(cwd, {
|
|
13609
|
+
from: fromHead,
|
|
13610
|
+
to: head,
|
|
13611
|
+
fromBase: row.fromBase,
|
|
13612
|
+
toBase: row.toBase
|
|
13613
|
+
});
|
|
13614
|
+
restackExact.set(row, known);
|
|
13615
|
+
}
|
|
13616
|
+
return known;
|
|
13617
|
+
};
|
|
12753
13618
|
const edges = new Map;
|
|
12754
13619
|
for (const row of rows)
|
|
12755
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
13620
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head && exact(row, row.fromHead, row.head))
|
|
12756
13621
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
12757
13622
|
const seen = new Set([from]);
|
|
12758
13623
|
const queue = [from];
|
|
@@ -12786,9 +13651,57 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
12786
13651
|
return key;
|
|
12787
13652
|
};
|
|
12788
13653
|
const keyFor = (base) => keysAt(base, head);
|
|
12789
|
-
const
|
|
13654
|
+
const driftCache = new Map;
|
|
13655
|
+
const driftOf = (row) => {
|
|
13656
|
+
if (driftCache.has(row))
|
|
13657
|
+
return driftCache.get(row) ?? null;
|
|
13658
|
+
let drift = null;
|
|
13659
|
+
const toBase = row.base ?? fallbackBase;
|
|
13660
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
13661
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
13662
|
+
if (row.head && head && row.head !== head && toBaseSha && fromBase && !untrusted(reasonsOf(row)))
|
|
13663
|
+
drift = verdictDrift(cwd, {
|
|
13664
|
+
from: row.head,
|
|
13665
|
+
to: head,
|
|
13666
|
+
fromBase,
|
|
13667
|
+
toBase: toBaseSha,
|
|
13668
|
+
kind: typeof row.kind === "string" ? row.kind : "review"
|
|
13669
|
+
});
|
|
13670
|
+
driftCache.set(row, drift);
|
|
13671
|
+
return drift;
|
|
13672
|
+
};
|
|
13673
|
+
const exactCache = new Map;
|
|
13674
|
+
const exactCarry = (row) => {
|
|
13675
|
+
if (FAILING.has(String(row.result)))
|
|
13676
|
+
return true;
|
|
13677
|
+
let exact = exactCache.get(row);
|
|
13678
|
+
if (exact === undefined) {
|
|
13679
|
+
const toBase = row.base ?? fallbackBase;
|
|
13680
|
+
const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
|
|
13681
|
+
const fromBase = row.baseSha ?? toBaseSha;
|
|
13682
|
+
exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
|
|
13683
|
+
exactCache.set(row, exact);
|
|
13684
|
+
}
|
|
13685
|
+
return exact;
|
|
13686
|
+
};
|
|
13687
|
+
const keyBasis = (row) => {
|
|
12790
13688
|
const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
|
|
12791
|
-
return basis === "
|
|
13689
|
+
return basis === "carried" && !exactCarry(row) ? "stale" : basis;
|
|
13690
|
+
};
|
|
13691
|
+
const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
|
|
13692
|
+
const carryOf = (row) => {
|
|
13693
|
+
if (keyBasis(row) !== "stale")
|
|
13694
|
+
return null;
|
|
13695
|
+
if (restacked(row))
|
|
13696
|
+
return null;
|
|
13697
|
+
const drift = driftOf(row);
|
|
13698
|
+
return drift?.carries ? { rule: drift.rule, since: drift.since, files: drift.files } : null;
|
|
13699
|
+
};
|
|
13700
|
+
const basisOf = (row) => {
|
|
13701
|
+
const basis = keyBasis(row);
|
|
13702
|
+
if (basis !== "stale")
|
|
13703
|
+
return basis;
|
|
13704
|
+
return restacked(row) || carryOf(row) ? "carried" : basis;
|
|
12792
13705
|
};
|
|
12793
13706
|
const sameCode = (failed, row) => {
|
|
12794
13707
|
if (failed.head && failed.head === row.head)
|
|
@@ -12796,7 +13709,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
12796
13709
|
const base = failed.base ?? fallbackBase;
|
|
12797
13710
|
const stored = (row.base ?? fallbackBase) === base && row.baseSha !== null && row.baseSha === failed.baseSha;
|
|
12798
13711
|
const keys = stored ? { patchId: row.patchId, diffHash: row.diffHash } : keysAt(base, row.head);
|
|
12799
|
-
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
13712
|
+
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(null, rows, branch, failed.head, row.head);
|
|
12800
13713
|
};
|
|
12801
13714
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
12802
13715
|
const failureAuthorCache = new Map;
|
|
@@ -12852,6 +13765,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
12852
13765
|
independent: independence.length === 0,
|
|
12853
13766
|
accepted: reasons.length === 0,
|
|
12854
13767
|
reasons,
|
|
13768
|
+
carry: basis === "carried" ? carryOf(row) : null,
|
|
12855
13769
|
verdict: row
|
|
12856
13770
|
});
|
|
12857
13771
|
if (reasons.length === 0) {
|
|
@@ -12880,7 +13794,12 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
12880
13794
|
verdict: failing?.verdict ?? null,
|
|
12881
13795
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
12882
13796
|
};
|
|
12883
|
-
const
|
|
13797
|
+
const latestSelf = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && (STRONG_RESULTS.has(String(entry.verdict.result)) || FAILING.has(String(entry.verdict.result))));
|
|
13798
|
+
const selfEntry = latestSelf && STRONG_RESULTS.has(String(latestSelf.verdict.result)) ? latestSelf : undefined;
|
|
13799
|
+
const carryEntry = verdicts.find((entry) => entry.verdict === accepted.verdict && entry.accepted) ?? newestCurrent;
|
|
13800
|
+
const lastPass = accepted.accepted || failing || tampered.length ? undefined : verdicts.findLast((entry) => !entry.current && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
13801
|
+
const drift = lastPass ? driftOf(lastPass.verdict) : null;
|
|
13802
|
+
const reverify = drift && !drift.carries && drift.focused && drift.files ? { scope: "focused", since: drift.since, files: drift.files } : null;
|
|
12884
13803
|
return {
|
|
12885
13804
|
branch,
|
|
12886
13805
|
head,
|
|
@@ -12889,6 +13808,8 @@ function checkVerdicts(cwd, branch, ledgerRows) {
|
|
|
12889
13808
|
base: newestCurrent?.verdict.base ?? fallbackBase,
|
|
12890
13809
|
patchId: keyFor(newestCurrent?.verdict.base ?? fallbackBase).patchId,
|
|
12891
13810
|
current: { basis: newestCurrent?.basis ?? "none", verdict: newestCurrent?.verdict ?? null },
|
|
13811
|
+
carry: carryEntry?.carry ?? null,
|
|
13812
|
+
reverify,
|
|
12892
13813
|
accepted,
|
|
12893
13814
|
verdicts,
|
|
12894
13815
|
authors: [...authors].toSorted(),
|
|
@@ -12930,6 +13851,7 @@ var RULES = {
|
|
|
12930
13851
|
self: "verdict:self",
|
|
12931
13852
|
nonAuthor: "verdict:non-author",
|
|
12932
13853
|
verified: "verdict:verified",
|
|
13854
|
+
lane: "verdict:lane",
|
|
12933
13855
|
product: "decision:product",
|
|
12934
13856
|
plan: "plan"
|
|
12935
13857
|
};
|
|
@@ -12952,6 +13874,7 @@ var normalizedScope = (scope) => ({
|
|
|
12952
13874
|
function deriveRequirements(judgment, scope, constraints, verification = "self") {
|
|
12953
13875
|
const out = [];
|
|
12954
13876
|
const base = { scope, dependentAction: null };
|
|
13877
|
+
const proportional = verification === "proportional";
|
|
12955
13878
|
if (judgment.behaviorChange)
|
|
12956
13879
|
out.push(derived2({
|
|
12957
13880
|
...base,
|
|
@@ -12962,7 +13885,17 @@ function deriveRequirements(judgment, scope, constraints, verification = "self")
|
|
|
12962
13885
|
before: "close",
|
|
12963
13886
|
acceptanceAllowed: false
|
|
12964
13887
|
}));
|
|
12965
|
-
if (
|
|
13888
|
+
if (proportional && judgment.riskTier === "normal")
|
|
13889
|
+
out.push(derived2({
|
|
13890
|
+
...base,
|
|
13891
|
+
ruleId: RULES.lane,
|
|
13892
|
+
dimension: "review",
|
|
13893
|
+
reason: "The review lane computed from the diff decides the verdict: none needs none, self the author's own (shown as self-reviewed), focused and full a non-author one.",
|
|
13894
|
+
satisfaction: "Read the lane in `workit policy assess` or `workit pr status`; record what it asks: `workit ledger verdict tests-verified --self --how <what you ran>` for self, or hand focused and full to a verifier.",
|
|
13895
|
+
before: "close",
|
|
13896
|
+
acceptanceAllowed: false
|
|
13897
|
+
}));
|
|
13898
|
+
if (!proportional && judgment.behaviorChange && judgment.riskTier === "normal")
|
|
12966
13899
|
out.push(verification === "independent" ? derived2({
|
|
12967
13900
|
...base,
|
|
12968
13901
|
ruleId: RULES.nonAuthor,
|
|
@@ -13065,7 +13998,7 @@ var productDecision = (task, ledger) => {
|
|
|
13065
13998
|
};
|
|
13066
13999
|
};
|
|
13067
14000
|
var planRecorded = (task, root) => {
|
|
13068
|
-
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" &&
|
|
14001
|
+
const cited = (latestJudgment(task)?.refs ?? []).find((ref) => ref.kind === "external" || ref.kind === "file" && existsSync5(path15.resolve(root, ref.path)));
|
|
13069
14002
|
if (cited)
|
|
13070
14003
|
return {
|
|
13071
14004
|
met: true,
|
|
@@ -13079,14 +14012,30 @@ var planRecorded = (task, root) => {
|
|
|
13079
14012
|
decisionIds: []
|
|
13080
14013
|
};
|
|
13081
14014
|
};
|
|
13082
|
-
var
|
|
14015
|
+
var workingLane = (task, root) => branchLane(root, { head: null, workingTree: true, judgment: latestJudgment(task) });
|
|
14016
|
+
var laneVerdict = (task, root, ledger) => {
|
|
14017
|
+
if (!ledger.branch() && latestJudgment(task)?.behaviorChange === false)
|
|
14018
|
+
return {
|
|
14019
|
+
met: true,
|
|
14020
|
+
reason: "no branch to read a lane from, and the judgment records no behavior change",
|
|
14021
|
+
decisionIds: []
|
|
14022
|
+
};
|
|
14023
|
+
const lane = workingLane(task, root);
|
|
14024
|
+
const label = `lane ${lane.lane} (${lane.reasons.join("; ")})`;
|
|
14025
|
+
const need = laneNeed(lane.lane);
|
|
14026
|
+
if (need === "none")
|
|
14027
|
+
return { met: true, reason: `${label}: no verdict needed`, decisionIds: [] };
|
|
14028
|
+
const status = verdict(need === "self" ? RULES.self : RULES.nonAuthor, root, ledger);
|
|
14029
|
+
return { ...status, reason: `${label}: ${status.reason}` };
|
|
14030
|
+
};
|
|
14031
|
+
var verdict = (ruleId, root, ledger) => {
|
|
13083
14032
|
const branch = ledger.branch();
|
|
13084
14033
|
if (!branch)
|
|
13085
14034
|
return { met: false, reason: "no branch is checked out to hold a verdict", decisionIds: [] };
|
|
13086
14035
|
const check = checkVerdicts(root, branch, ledger.rows());
|
|
13087
14036
|
const failing = check.accepted.reasons.includes("failing_verdict");
|
|
13088
14037
|
const strong = (entry) => STRONG_RESULTS.has(String(entry.verdict.result));
|
|
13089
|
-
const met = !failing && (
|
|
14038
|
+
const met = !failing && (ruleId === RULES.verified ? check.verdicts.find((entry) => entry.accepted && entry.kind === "live" && String(entry.verdict.result) === "verified") : ruleId === RULES.nonAuthor ? check.verdicts.find((entry) => entry.accepted && strong(entry)) : check.verdicts.find((entry) => entry.current && strong(entry)));
|
|
13090
14039
|
if (met)
|
|
13091
14040
|
return {
|
|
13092
14041
|
met: true,
|
|
@@ -13094,7 +14043,7 @@ var verdict = (requirement, root, ledger) => {
|
|
|
13094
14043
|
decisionIds: []
|
|
13095
14044
|
};
|
|
13096
14045
|
const why = failing ? "a current independent verdict fails" : check.verdicts.some((entry) => entry.current && entry.verdict.result === "type-check-only") ? "type-check-only does not prove a behavior change" : (check.accepted.reasons.join(", ") || "no_verdict").replace(/_/g, " ");
|
|
13097
|
-
const unblock =
|
|
14046
|
+
const unblock = ruleId === RULES.self ? "record your own verdict after an observed `workit check test`: `workit ledger verdict tests-verified --self --how <what you ran>` (shown as self-reviewed), or have a verifier record one" : `an independent session records \`workit ledger verdict verified${ruleId === RULES.verified ? " --kind live" : ""} --how <what it exercised>\``;
|
|
13098
14047
|
return { met: false, reason: `${why}: ${unblock}`, decisionIds: [] };
|
|
13099
14048
|
};
|
|
13100
14049
|
function derivedRuleStatus(task, requirement, root, ledger) {
|
|
@@ -13106,7 +14055,9 @@ function derivedRuleStatus(task, requirement, root, ledger) {
|
|
|
13106
14055
|
case RULES.self:
|
|
13107
14056
|
case RULES.nonAuthor:
|
|
13108
14057
|
case RULES.verified:
|
|
13109
|
-
return verdict(requirement, root, ledger);
|
|
14058
|
+
return verdict(requirement.ruleId, root, ledger);
|
|
14059
|
+
case RULES.lane:
|
|
14060
|
+
return laneVerdict(task, root, ledger);
|
|
13110
14061
|
default:
|
|
13111
14062
|
return null;
|
|
13112
14063
|
}
|
|
@@ -13125,14 +14076,14 @@ function writeBlockers(task, root) {
|
|
|
13125
14076
|
|
|
13126
14077
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
13127
14078
|
var digestBytes = (value) => createHash11("sha256").update(value).digest("hex");
|
|
13128
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
13129
|
-
var relative = (root, candidate) =>
|
|
14079
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path16.sep}`);
|
|
14080
|
+
var relative = (root, candidate) => path16.relative(root, candidate).split(path16.sep).join("/") || ".";
|
|
13130
14081
|
var canonicalPath = (value) => {
|
|
13131
14082
|
if (typeof value !== "string" || !value)
|
|
13132
14083
|
return null;
|
|
13133
14084
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
13134
14085
|
return null;
|
|
13135
|
-
const normalized =
|
|
14086
|
+
const normalized = path16.posix.normalize(value);
|
|
13136
14087
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
13137
14088
|
return null;
|
|
13138
14089
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -13180,14 +14131,14 @@ var gitPaths = (root) => {
|
|
|
13180
14131
|
git: !/not a git repository/i.test(stderr)
|
|
13181
14132
|
};
|
|
13182
14133
|
}
|
|
13183
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14134
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")) : [];
|
|
13184
14135
|
const stagedDeleted = spawnSync9("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
13185
14136
|
if (stagedDeleted.status !== 0) {
|
|
13186
14137
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
13187
14138
|
if (!/not a git repository/i.test(stderr))
|
|
13188
14139
|
return { paths, uncertain: true, git: true };
|
|
13189
14140
|
} else if (stagedDeleted.stdout) {
|
|
13190
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
14141
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path16.sep).join("/")));
|
|
13191
14142
|
}
|
|
13192
14143
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
13193
14144
|
};
|
|
@@ -13202,7 +14153,7 @@ var walk = (root, directory, output) => {
|
|
|
13202
14153
|
for (const name of names) {
|
|
13203
14154
|
if (name === ".git" || name === ".workit")
|
|
13204
14155
|
continue;
|
|
13205
|
-
const target =
|
|
14156
|
+
const target = path16.join(directory, name);
|
|
13206
14157
|
let stat;
|
|
13207
14158
|
try {
|
|
13208
14159
|
stat = fs12.lstatSync(target);
|
|
@@ -13220,12 +14171,12 @@ var walk = (root, directory, output) => {
|
|
|
13220
14171
|
var scopeRoots = (root, scope) => {
|
|
13221
14172
|
const roots = [];
|
|
13222
14173
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
13223
|
-
const target =
|
|
14174
|
+
const target = path16.resolve(root, item);
|
|
13224
14175
|
if (!inside(root, target))
|
|
13225
14176
|
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` }] });
|
|
13226
14177
|
let ancestor = target;
|
|
13227
14178
|
while (!fs12.existsSync(ancestor) && ancestor !== root)
|
|
13228
|
-
ancestor =
|
|
14179
|
+
ancestor = path16.dirname(ancestor);
|
|
13229
14180
|
try {
|
|
13230
14181
|
if (!inside(root, fs12.realpathSync(ancestor)))
|
|
13231
14182
|
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` }] });
|
|
@@ -13277,7 +14228,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
13277
14228
|
for (const item of [...names].toSorted()) {
|
|
13278
14229
|
if (!scopeMatches(item, normalizedScope))
|
|
13279
14230
|
continue;
|
|
13280
|
-
const target =
|
|
14231
|
+
const target = path16.join(checkout, item);
|
|
13281
14232
|
try {
|
|
13282
14233
|
const stat = fs12.lstatSync(target);
|
|
13283
14234
|
if (stat.isSymbolicLink()) {
|
|
@@ -13461,23 +14412,23 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
13461
14412
|
continue;
|
|
13462
14413
|
if (!reference.digest)
|
|
13463
14414
|
return failure2("invalid_input", "document references require a byte digest");
|
|
13464
|
-
const target =
|
|
13465
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
14415
|
+
const target = path16.resolve(root, reference.path);
|
|
14416
|
+
if (target !== root && !target.startsWith(`${root}${path16.sep}`))
|
|
13466
14417
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
13467
14418
|
fields: [
|
|
13468
14419
|
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
13469
14420
|
]
|
|
13470
14421
|
});
|
|
13471
14422
|
try {
|
|
13472
|
-
const below =
|
|
14423
|
+
const below = path16.relative(root, target);
|
|
13473
14424
|
let current = root;
|
|
13474
|
-
for (const segment of below.split(
|
|
14425
|
+
for (const segment of below.split(path16.sep)) {
|
|
13475
14426
|
if (!segment)
|
|
13476
14427
|
continue;
|
|
13477
|
-
current =
|
|
14428
|
+
current = path16.join(current, segment);
|
|
13478
14429
|
const stat = fs12.lstatSync(current);
|
|
13479
14430
|
const real = fs12.realpathSync(current);
|
|
13480
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
14431
|
+
if (real !== root && !real.startsWith(`${root}${path16.sep}`))
|
|
13481
14432
|
return failure2("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
13482
14433
|
fields: [
|
|
13483
14434
|
{
|
|
@@ -14398,7 +15349,7 @@ var judgeInputSummary = (raw) => {
|
|
|
14398
15349
|
|
|
14399
15350
|
// packages/workit-core/src/autonomy.ts
|
|
14400
15351
|
import os4 from "node:os";
|
|
14401
|
-
import
|
|
15352
|
+
import path17 from "node:path";
|
|
14402
15353
|
var accountHome = () => {
|
|
14403
15354
|
try {
|
|
14404
15355
|
return os4.userInfo().homedir;
|
|
@@ -14407,10 +15358,10 @@ var accountHome = () => {
|
|
|
14407
15358
|
}
|
|
14408
15359
|
};
|
|
14409
15360
|
var grantsHome = { resolve: accountHome };
|
|
14410
|
-
var grantsDir = () =>
|
|
15361
|
+
var grantsDir = () => path17.join(grantsHome.resolve(), ".config", "workit");
|
|
14411
15362
|
var grantsOverride = () => {
|
|
14412
|
-
const configured =
|
|
14413
|
-
return configured ===
|
|
15363
|
+
const configured = path17.resolve(resolveConfigDir());
|
|
15364
|
+
return configured === path17.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 ${path17.join(grantsDir(), "workspaces.json")}, so the D4 defaults apply`;
|
|
14414
15365
|
};
|
|
14415
15366
|
var GRANT_KINDS = ["push", "pr", "merge", "release", "rerun"];
|
|
14416
15367
|
var DEFAULT_GRANTS = Object.freeze({
|
|
@@ -14463,8 +15414,16 @@ function endpointView(entry, context) {
|
|
|
14463
15414
|
...issue ? { endpointIssue: issue } : {}
|
|
14464
15415
|
};
|
|
14465
15416
|
}
|
|
14466
|
-
var
|
|
14467
|
-
|
|
15417
|
+
var VERIFICATION_MODES = [
|
|
15418
|
+
"proportional",
|
|
15419
|
+
"self",
|
|
15420
|
+
"independent"
|
|
15421
|
+
];
|
|
15422
|
+
var DEFAULT_VERIFICATION = "proportional";
|
|
15423
|
+
var verificationOf = (entry) => {
|
|
15424
|
+
const value = entry?.verification;
|
|
15425
|
+
return VERIFICATION_MODES.includes(value) ? value : DEFAULT_VERIFICATION;
|
|
15426
|
+
};
|
|
14468
15427
|
var isGrantValue = (value) => value === true || value === false || value === "verified";
|
|
14469
15428
|
var normalized = (kind, value) => value === "verified" && kind !== "merge" ? true : value;
|
|
14470
15429
|
var fromAutoApprove = (value) => {
|
|
@@ -16423,7 +17382,7 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
16423
17382
|
return null;
|
|
16424
17383
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
16425
17384
|
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
16426
|
-
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [
|
|
17385
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path18.resolve(store.root, ref.path)] : []));
|
|
16427
17386
|
const observedFiles = filesKey(files);
|
|
16428
17387
|
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
16429
17388
|
if (!compact.ok || !compact.data)
|
|
@@ -16501,14 +17460,14 @@ var turnMarker = (input) => {
|
|
|
16501
17460
|
if (location instanceof Error)
|
|
16502
17461
|
return null;
|
|
16503
17462
|
const session = [input.host, input.session.id, input.session.agentId ?? ""].join("\x00");
|
|
16504
|
-
return
|
|
17463
|
+
return path18.join(location.dir, "hooks", `turn-${digestOf(session).slice(0, 32)}.json`);
|
|
16505
17464
|
} catch {
|
|
16506
17465
|
return null;
|
|
16507
17466
|
}
|
|
16508
17467
|
};
|
|
16509
17468
|
var writeTurnDigest = (file, digest) => {
|
|
16510
17469
|
try {
|
|
16511
|
-
fs13.mkdirSync(
|
|
17470
|
+
fs13.mkdirSync(path18.dirname(file), { recursive: true });
|
|
16512
17471
|
fs13.writeFileSync(file, `${JSON.stringify({ digest })}
|
|
16513
17472
|
`);
|
|
16514
17473
|
} catch {}
|
|
@@ -16523,7 +17482,7 @@ var pruneTurnDigests = (dir, now) => {
|
|
|
16523
17482
|
for (const name of fs13.readdirSync(dir)) {
|
|
16524
17483
|
if (!name.startsWith("turn-"))
|
|
16525
17484
|
continue;
|
|
16526
|
-
const file =
|
|
17485
|
+
const file = path18.join(dir, name);
|
|
16527
17486
|
try {
|
|
16528
17487
|
if (now - fs13.statSync(file).mtimeMs > TURN_TTL_MS)
|
|
16529
17488
|
fs13.rmSync(file, { force: true });
|
|
@@ -16553,7 +17512,7 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16553
17512
|
const file = turnMarker(input);
|
|
16554
17513
|
if (!file)
|
|
16555
17514
|
return;
|
|
16556
|
-
pruneTurnDigests(
|
|
17515
|
+
pruneTurnDigests(path18.dirname(file), Date.now());
|
|
16557
17516
|
if (text)
|
|
16558
17517
|
writeTurnDigest(file, digestOf(text));
|
|
16559
17518
|
else
|
|
@@ -16561,20 +17520,20 @@ var seedTurnContext = (input, descriptor, text = turnContextText(input, descript
|
|
|
16561
17520
|
};
|
|
16562
17521
|
// packages/workit-core/src/core/route-intent.ts
|
|
16563
17522
|
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
16564
|
-
import { existsSync as
|
|
16565
|
-
import
|
|
17523
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
17524
|
+
import path21 from "node:path";
|
|
16566
17525
|
|
|
16567
17526
|
// packages/workit-core/src/hooks/raw-git.ts
|
|
16568
17527
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
16569
17528
|
import { createHash as createHash14 } from "node:crypto";
|
|
16570
17529
|
import fs15 from "node:fs";
|
|
16571
17530
|
import os5 from "node:os";
|
|
16572
|
-
import
|
|
17531
|
+
import path20 from "node:path";
|
|
16573
17532
|
|
|
16574
17533
|
// packages/workit-core/src/hooks/session-marker.ts
|
|
16575
17534
|
import { createHash as createHash13 } from "node:crypto";
|
|
16576
17535
|
import fs14 from "node:fs";
|
|
16577
|
-
import
|
|
17536
|
+
import path19 from "node:path";
|
|
16578
17537
|
var sessionMarkerFile = (input, session, store) => {
|
|
16579
17538
|
let dir = store;
|
|
16580
17539
|
if (dir === undefined) {
|
|
@@ -16584,7 +17543,7 @@ var sessionMarkerFile = (input, session, store) => {
|
|
|
16584
17543
|
dir = location.dir;
|
|
16585
17544
|
}
|
|
16586
17545
|
const name = createHash13("sha256").update(`${input.host}\x00${session}`).digest("hex").slice(0, 32);
|
|
16587
|
-
return
|
|
17546
|
+
return path19.join(dir, "hooks", `skills-${name}.json`);
|
|
16588
17547
|
};
|
|
16589
17548
|
var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
|
|
16590
17549
|
var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
|
|
@@ -16611,7 +17570,7 @@ var parseMarker = (text) => {
|
|
|
16611
17570
|
};
|
|
16612
17571
|
var readSessionMarker = (file) => parseMarker(readRaw(file));
|
|
16613
17572
|
var writeAtomic = (file, marker) => {
|
|
16614
|
-
fs14.mkdirSync(
|
|
17573
|
+
fs14.mkdirSync(path19.dirname(file), { recursive: true });
|
|
16615
17574
|
const temp = `${file}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
16616
17575
|
try {
|
|
16617
17576
|
fs14.writeFileSync(temp, JSON.stringify(marker));
|
|
@@ -16898,6 +17857,18 @@ var substitutions = (body) => {
|
|
|
16898
17857
|
};
|
|
16899
17858
|
var PLAIN_POSIX = /[^\s'"\\`$(){},<>|;&#]+/uy;
|
|
16900
17859
|
var PLAIN_POWERSHELL = /[^\s'"`(){}<>|;&#]+/uy;
|
|
17860
|
+
var expandsInQuotes = (body, dialect) => {
|
|
17861
|
+
const escape = dialect === "posix" ? "\\" : "`";
|
|
17862
|
+
for (let index = 0;index < body.length; index++) {
|
|
17863
|
+
const char = body[index];
|
|
17864
|
+
if (char === escape)
|
|
17865
|
+
index++;
|
|
17866
|
+
else if (char === "$" || dialect === "posix" && char === "`")
|
|
17867
|
+
return true;
|
|
17868
|
+
}
|
|
17869
|
+
return false;
|
|
17870
|
+
};
|
|
17871
|
+
var EXPANDING_RUN = /[*?[]/u;
|
|
16901
17872
|
var arithmeticEnd = (command, start) => {
|
|
16902
17873
|
const end = substitutionEnd(command, start);
|
|
16903
17874
|
return command[end - 1] === ")" && end > start + 2 ? end : 0;
|
|
@@ -16916,6 +17887,7 @@ var tokenize = (command, dialect) => {
|
|
|
16916
17887
|
let current = { words: [], redirects: [] };
|
|
16917
17888
|
let word = "";
|
|
16918
17889
|
let inWord = false;
|
|
17890
|
+
let expanding = false;
|
|
16919
17891
|
let pending = null;
|
|
16920
17892
|
const heredocs = [];
|
|
16921
17893
|
const escape = posix ? "\\" : "`";
|
|
@@ -16930,11 +17902,16 @@ var tokenize = (command, dialect) => {
|
|
|
16930
17902
|
heredocs.push({ delimiter: text.replace(/^-/, ""), owner: current });
|
|
16931
17903
|
else if (pending === "herestring")
|
|
16932
17904
|
(current.stdin ??= []).push(text);
|
|
16933
|
-
else if (pending !== "input")
|
|
17905
|
+
else if (pending !== "input") {
|
|
17906
|
+
const at = current.words.length;
|
|
16934
17907
|
current.words.push(...MARKERS.test(word) ? expandBraces(word) : [word]);
|
|
17908
|
+
if (expanding || !posix && word.startsWith("@"))
|
|
17909
|
+
(current.expands ??= []).push(...current.words.slice(at).map((_, i) => at + i));
|
|
17910
|
+
}
|
|
16935
17911
|
pending = null;
|
|
16936
17912
|
word = "";
|
|
16937
17913
|
inWord = false;
|
|
17914
|
+
expanding = false;
|
|
16938
17915
|
};
|
|
16939
17916
|
const flushSegment = () => {
|
|
16940
17917
|
flushWord();
|
|
@@ -16949,6 +17926,7 @@ var tokenize = (command, dialect) => {
|
|
|
16949
17926
|
};
|
|
16950
17927
|
const substitution = (inner, text) => {
|
|
16951
17928
|
(current.subs ??= []).push(inner);
|
|
17929
|
+
expanding = true;
|
|
16952
17930
|
word += text;
|
|
16953
17931
|
inWord = true;
|
|
16954
17932
|
};
|
|
@@ -16959,6 +17937,8 @@ var tokenize = (command, dialect) => {
|
|
|
16959
17937
|
if (run) {
|
|
16960
17938
|
word += run[0];
|
|
16961
17939
|
inWord = true;
|
|
17940
|
+
if (EXPANDING_RUN.test(run[0]))
|
|
17941
|
+
expanding = true;
|
|
16962
17942
|
index += run[0].length - 1;
|
|
16963
17943
|
continue;
|
|
16964
17944
|
}
|
|
@@ -16987,6 +17967,8 @@ var tokenize = (command, dialect) => {
|
|
|
16987
17967
|
const body = command.slice(index + 1, end < 0 ? undefined : end);
|
|
16988
17968
|
if (posix && char === '"' && /\$\(|`/.test(body))
|
|
16989
17969
|
(current.subs ??= []).push(...substitutions(body));
|
|
17970
|
+
if (char === '"' && expandsInQuotes(body, dialect))
|
|
17971
|
+
expanding = true;
|
|
16990
17972
|
word += unquote(body, char, dialect);
|
|
16991
17973
|
inWord = true;
|
|
16992
17974
|
index = end < 0 ? command.length : end;
|
|
@@ -17032,6 +18014,8 @@ var tokenize = (command, dialect) => {
|
|
|
17032
18014
|
if (char === "(" && inWord && word.endsWith("$")) {
|
|
17033
18015
|
word = word.slice(0, -1);
|
|
17034
18016
|
inWord = word !== "";
|
|
18017
|
+
if (!inWord)
|
|
18018
|
+
expanding = false;
|
|
17035
18019
|
}
|
|
17036
18020
|
group(char === "(" ? "open" : "close");
|
|
17037
18021
|
} else if (!posix && (char === "{" || char === "}")) {
|
|
@@ -17085,6 +18069,8 @@ var tokenize = (command, dialect) => {
|
|
|
17085
18069
|
else if (posix && char === "}" && word.includes(OPEN))
|
|
17086
18070
|
word += CLOSE;
|
|
17087
18071
|
else {
|
|
18072
|
+
if (char === "$")
|
|
18073
|
+
expanding = true;
|
|
17088
18074
|
word += char;
|
|
17089
18075
|
inWord = true;
|
|
17090
18076
|
}
|
|
@@ -17142,14 +18128,14 @@ var GIT_CONFIG_ENV = /^GIT_(?:CONFIG\w*|DIR|COMMON_DIR|WORK_TREE)$/;
|
|
|
17142
18128
|
var ASSIGNMENT = /^([A-Za-z_]\w*)=([\s\S]*)$/;
|
|
17143
18129
|
var MAX_DEPTH = 4;
|
|
17144
18130
|
var MAX_EXPANSIONS = 16;
|
|
17145
|
-
var isAbsolute = (value) =>
|
|
18131
|
+
var isAbsolute = (value) => path20.isAbsolute(value) || /^[A-Za-z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
17146
18132
|
var HERE = /^(?:\$PWD|\$\{PWD\}|\$\(\s*pwd\s*\)|`pwd`|\$\(\s*git\s+rev-parse\s+--show-toplevel\s*\))(?=$|[\\/])/;
|
|
17147
18133
|
var joinDir = (base, raw) => {
|
|
17148
18134
|
const next = raw.replace(HERE, ".");
|
|
17149
18135
|
if (base === undefined || /[$`*?]/.test(next) || next === "-")
|
|
17150
18136
|
return;
|
|
17151
|
-
const expanded = next === "~" || next.startsWith("~/") ?
|
|
17152
|
-
return base === null || isAbsolute(expanded) ? expanded :
|
|
18137
|
+
const expanded = next === "~" || next.startsWith("~/") ? path20.join(os5.homedir(), next.slice(1)) : next;
|
|
18138
|
+
return base === null || isAbsolute(expanded) ? expanded : path20.join(base, expanded);
|
|
17153
18139
|
};
|
|
17154
18140
|
var rebase = (base, inner) => inner === null ? base : inner === undefined ? undefined : joinDir(base, inner);
|
|
17155
18141
|
var cdTarget = (words) => {
|
|
@@ -17529,8 +18515,17 @@ function walk2(command, dialect, depth, seen) {
|
|
|
17529
18515
|
visit(next, undefined, hops + 1);
|
|
17530
18516
|
};
|
|
17531
18517
|
const wrapped = seen ? null : workitCheckTail(words);
|
|
17532
|
-
if (wrapped)
|
|
17533
|
-
|
|
18518
|
+
if (wrapped) {
|
|
18519
|
+
const outer = dir;
|
|
18520
|
+
if (wrapped.cwd !== null)
|
|
18521
|
+
dir = joinDir(dir, wrapped.cwd);
|
|
18522
|
+
if ("script" in wrapped)
|
|
18523
|
+
nested(wrapped.script, "posix");
|
|
18524
|
+
else
|
|
18525
|
+
runs([wrapped.argv]);
|
|
18526
|
+
dir = outer;
|
|
18527
|
+
return;
|
|
18528
|
+
}
|
|
17534
18529
|
if (CD.has(name)) {
|
|
17535
18530
|
const target = cdTarget(words);
|
|
17536
18531
|
dir = target === null ? os5.homedir() : joinDir(dir, target);
|
|
@@ -17631,14 +18626,14 @@ function workitCheckTail(words) {
|
|
|
17631
18626
|
return null;
|
|
17632
18627
|
} else if (!/^workit(?:\.exe|\.cmd)?$/.test(commandName(words[0])))
|
|
17633
18628
|
return null;
|
|
17634
|
-
const args = words.slice(at + 1);
|
|
18629
|
+
const { rest: args, cwd } = skipWorkitGlobals(words.slice(at + 1));
|
|
17635
18630
|
if (args[0] !== "check" && !(args[0] === "lock" && args[1] === "run"))
|
|
17636
18631
|
return null;
|
|
17637
18632
|
const split = args.indexOf("--");
|
|
17638
18633
|
const tail = split < 0 ? [] : args.slice(split + 1);
|
|
17639
18634
|
if (!tail.length)
|
|
17640
18635
|
return null;
|
|
17641
|
-
return args.slice(0, split).includes("--shell") ? { script: tail.join(" ") } : { argv: tail };
|
|
18636
|
+
return args.slice(0, split).includes("--shell") ? { script: tail.join(" "), cwd } : { argv: tail, cwd };
|
|
17642
18637
|
}
|
|
17643
18638
|
function simpleCommand(command, dialect = "posix", depth = 0) {
|
|
17644
18639
|
let segments;
|
|
@@ -17699,7 +18694,21 @@ function skipWorkitGlobals(args) {
|
|
|
17699
18694
|
} else if (args[at] !== "--json")
|
|
17700
18695
|
break;
|
|
17701
18696
|
}
|
|
17702
|
-
|
|
18697
|
+
const rest = [];
|
|
18698
|
+
for (;at < args.length; at++) {
|
|
18699
|
+
const arg = args[at];
|
|
18700
|
+
if (arg === "--") {
|
|
18701
|
+
rest.push(...args.slice(at));
|
|
18702
|
+
break;
|
|
18703
|
+
}
|
|
18704
|
+
if (arg.startsWith("--cwd="))
|
|
18705
|
+
cwd = arg.slice("--cwd=".length);
|
|
18706
|
+
else if (arg === "--cwd" && args[at + 1] !== undefined)
|
|
18707
|
+
cwd = args[++at];
|
|
18708
|
+
else
|
|
18709
|
+
rest.push(arg);
|
|
18710
|
+
}
|
|
18711
|
+
return { rest, cwd };
|
|
17703
18712
|
}
|
|
17704
18713
|
var PUSH_VALUE_OPTIONS = new Set(["-o", "--push-option", "--receive-pack", "--exec", "--repo"]);
|
|
17705
18714
|
var CURRENT_BRANCH = /^\$\(\s*git\s+(?:branch\s+--show-current|rev-parse\s+--abbrev-ref\s+HEAD|symbolic-ref\s+--short\s+HEAD)\s*\)$/;
|
|
@@ -18000,17 +19009,17 @@ var headSha2 = (repo) => {
|
|
|
18000
19009
|
if (!git || git.reftable)
|
|
18001
19010
|
return null;
|
|
18002
19011
|
try {
|
|
18003
|
-
const head = fs15.readFileSync(
|
|
19012
|
+
const head = fs15.readFileSync(path20.join(git.gitDir, "HEAD"), "utf8").trim();
|
|
18004
19013
|
if (/^[0-9a-f]{40,64}$/.test(head))
|
|
18005
19014
|
return head;
|
|
18006
19015
|
const ref = /^ref: (refs\/.+)$/.exec(head)?.[1];
|
|
18007
19016
|
if (!ref)
|
|
18008
19017
|
return null;
|
|
18009
|
-
const common =
|
|
19018
|
+
const common = path20.dirname(repo.location.dir);
|
|
18010
19019
|
try {
|
|
18011
|
-
return fs15.readFileSync(
|
|
19020
|
+
return fs15.readFileSync(path20.join(common, ref), "utf8").trim() || null;
|
|
18012
19021
|
} catch {}
|
|
18013
|
-
const packed = fs15.readFileSync(
|
|
19022
|
+
const packed = fs15.readFileSync(path20.join(common, "packed-refs"), "utf8");
|
|
18014
19023
|
return new RegExp(`^([0-9a-f]{40,64}) ${ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`, "m").exec(packed)?.[1] ?? null;
|
|
18015
19024
|
} catch {
|
|
18016
19025
|
return null;
|
|
@@ -18098,7 +19107,7 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18098
19107
|
const sub = invocation.args[0];
|
|
18099
19108
|
if (invocation.tool !== "git" || invocation.dir === undefined || !sub || sub.startsWith("-") || GIT_COMMANDS.has(sub) || depth >= MAX_DEPTH)
|
|
18100
19109
|
return [invocation];
|
|
18101
|
-
const dir =
|
|
19110
|
+
const dir = path20.resolve(root, invocation.dir ?? ".");
|
|
18102
19111
|
if (!fs15.existsSync(dir))
|
|
18103
19112
|
return [invocation];
|
|
18104
19113
|
const config = gitConfig(dir, invocation, cache);
|
|
@@ -18121,11 +19130,11 @@ var expandGitAliases = (root, invocation, cache = new Map, depth = 0) => {
|
|
|
18121
19130
|
var normalizedUrl = (url, dir) => {
|
|
18122
19131
|
const trimmed = url.trim();
|
|
18123
19132
|
if (/^file:\/\//i.test(trimmed))
|
|
18124
|
-
return
|
|
19133
|
+
return path20.resolve(dir, trimmed.slice(7)).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18125
19134
|
const scheme = /^[a-z][a-z0-9+.-]*:\/\//i.exec(trimmed);
|
|
18126
19135
|
const scp = !scheme && /^(?:[^@/\\]+@)?[^/\\:]{2,}:/.test(trimmed);
|
|
18127
19136
|
if (!scheme && !scp)
|
|
18128
|
-
return
|
|
19137
|
+
return path20.resolve(dir, trimmed).replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18129
19138
|
const value = scheme ? trimmed.slice(scheme[0].length).replace(/^[^@/]*@/, "").replace(/^([^/:]+):\d+(?=\/)/, "$1") : trimmed.replace(/^[^@/]*@/, "").replace(":", "/");
|
|
18130
19139
|
return value.toLowerCase().replace(/\/{2,}/g, "/").replace(/^www\./, "").replace(/\/+$/, "").replace(/\.git$/, "");
|
|
18131
19140
|
};
|
|
@@ -18349,7 +19358,7 @@ var actionsIn = (cwd, command, dialect, cache = new Map) => {
|
|
|
18349
19358
|
const out = [];
|
|
18350
19359
|
const repos = new Map;
|
|
18351
19360
|
const repoFor = (relative) => {
|
|
18352
|
-
const dir =
|
|
19361
|
+
const dir = path20.resolve(cwd, relative ?? ".");
|
|
18353
19362
|
if (!repos.has(dir)) {
|
|
18354
19363
|
const repo = repoAt(dir);
|
|
18355
19364
|
repos.set(dir, repo && isWorkitWorkspace(repo) ? repo : null);
|
|
@@ -18435,7 +19444,7 @@ var analysisDenial = (error) => ({
|
|
|
18435
19444
|
});
|
|
18436
19445
|
var NEXT_COMMAND = "next";
|
|
18437
19446
|
var MARKER_TTL_SECONDS = 3600;
|
|
18438
|
-
var markerFile = (repo, session, key) =>
|
|
19447
|
+
var markerFile = (repo, session, key) => path20.join(repo.location.dir, "hooks", `commit-${createHash14("sha256").update(`${session}\x00${key}`).digest("hex").slice(0, 32)}.json`);
|
|
18439
19448
|
var callKey = (toolUseId, command) => toolUseId ?? `cmd:${createHash14("sha256").update(command).digest("hex")}`;
|
|
18440
19449
|
function noteRawCommit(input, command, key) {
|
|
18441
19450
|
try {
|
|
@@ -18446,7 +19455,7 @@ function noteRawCommit(input, command, key) {
|
|
|
18446
19455
|
if (action.kind !== "commit")
|
|
18447
19456
|
continue;
|
|
18448
19457
|
const file = markerFile(repo, input.session.id, key);
|
|
18449
|
-
fs15.mkdirSync(
|
|
19458
|
+
fs15.mkdirSync(path20.dirname(file), { recursive: true });
|
|
18450
19459
|
fs15.writeFileSync(file, `${JSON.stringify({ head: headSha2(repo), at })}
|
|
18451
19460
|
`);
|
|
18452
19461
|
}
|
|
@@ -18677,8 +19686,8 @@ var judgedDir = (root, dir) => {
|
|
|
18677
19686
|
return null;
|
|
18678
19687
|
if (dir === null)
|
|
18679
19688
|
return root;
|
|
18680
|
-
const resolved =
|
|
18681
|
-
return
|
|
19689
|
+
const resolved = path21.resolve(root, dir);
|
|
19690
|
+
return existsSync7(resolved) && inWorkitWorkspace(resolved) ? resolved : null;
|
|
18682
19691
|
};
|
|
18683
19692
|
var shellBranchPolicyViolation = (root, command, dialect = "posix") => {
|
|
18684
19693
|
let judged = false;
|
|
@@ -18711,10 +19720,57 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
18711
19720
|
}
|
|
18712
19721
|
return policy && !policy.ok ? { kind: "deny", reason: `branch_policy_denied: ${policy.error}`, unblock: null } : { kind: "none" };
|
|
18713
19722
|
};
|
|
19723
|
+
// packages/workit-core/src/hooks/delivery-verbs.ts
|
|
19724
|
+
var WORKIT_ALLOWED_VERBS = [
|
|
19725
|
+
"git",
|
|
19726
|
+
"pr",
|
|
19727
|
+
"ci",
|
|
19728
|
+
"stack",
|
|
19729
|
+
"release",
|
|
19730
|
+
"task",
|
|
19731
|
+
"ledger",
|
|
19732
|
+
"evidence",
|
|
19733
|
+
"finding",
|
|
19734
|
+
"decision",
|
|
19735
|
+
"worker",
|
|
19736
|
+
"verify-delivery",
|
|
19737
|
+
"policy",
|
|
19738
|
+
"doctor",
|
|
19739
|
+
"help"
|
|
19740
|
+
];
|
|
19741
|
+
var WORKIT_BYPASS_FLAGS = ["--unverified", "--overwrite-unintegrated"];
|
|
19742
|
+
var ALLOWED = new Set(WORKIT_ALLOWED_VERBS);
|
|
19743
|
+
var isBypass = (word) => WORKIT_BYPASS_FLAGS.some((flag) => word === flag || word.startsWith(`${flag}=`));
|
|
19744
|
+
var isWorkitWord = (word) => /^(?:.*[\\/])?workit(?:-cli)?$/u.test(word);
|
|
19745
|
+
var bypassReason = (flag) => `Workit: \`${flag}\` overrides a Workit gate, so the user decides this call (no allow rule covers it).`;
|
|
19746
|
+
var EXPANSION_REASON = "Workit: an expansion before `--` could become a bypass flag, so the user decides this call (no allow rule covers it).";
|
|
19747
|
+
var workitBypassFlag = (command, dialect = "posix") => {
|
|
19748
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
19749
|
+
if (segment.unanalyzable) {
|
|
19750
|
+
const text = command.replaceAll(/['"\\`]/gu, "");
|
|
19751
|
+
if (!/\bworkit\b/u.test(text))
|
|
19752
|
+
return null;
|
|
19753
|
+
const found = WORKIT_BYPASS_FLAGS.find((flag) => text.includes(flag));
|
|
19754
|
+
return found ? bypassReason(found) : null;
|
|
19755
|
+
}
|
|
19756
|
+
const start = segment.words.findIndex(isWorkitWord);
|
|
19757
|
+
if (start < 0)
|
|
19758
|
+
continue;
|
|
19759
|
+
const words = segment.words.slice(start + 1);
|
|
19760
|
+
const end = words.indexOf("--");
|
|
19761
|
+
const flag = (end < 0 ? words : words.slice(0, end)).find(isBypass);
|
|
19762
|
+
if (flag)
|
|
19763
|
+
return bypassReason(flag.split("=")[0] ?? flag);
|
|
19764
|
+
const limit = start + 1 + (end < 0 ? words.length : end);
|
|
19765
|
+
if (segment.expands?.some((index) => index > start && index < limit))
|
|
19766
|
+
return EXPANSION_REASON;
|
|
19767
|
+
}
|
|
19768
|
+
return null;
|
|
19769
|
+
};
|
|
18714
19770
|
// packages/workit-core/src/core/heavy-rules.ts
|
|
18715
19771
|
import fs16 from "node:fs";
|
|
18716
19772
|
import os6 from "node:os";
|
|
18717
|
-
import
|
|
19773
|
+
import path22 from "node:path";
|
|
18718
19774
|
var EMPTY2 = () => ({ deny: [], allow: [] });
|
|
18719
19775
|
var readText2 = (file) => {
|
|
18720
19776
|
try {
|
|
@@ -18755,19 +19811,19 @@ function parseJsonc(text) {
|
|
|
18755
19811
|
return JSON.parse(out.replace(/,(\s*[}\]])/gu, "$1"));
|
|
18756
19812
|
}
|
|
18757
19813
|
function gitTop(cwd) {
|
|
18758
|
-
for (let dir =
|
|
18759
|
-
if (fs16.existsSync(
|
|
19814
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
19815
|
+
if (fs16.existsSync(path22.join(dir, ".git")))
|
|
18760
19816
|
return dir;
|
|
18761
|
-
if (
|
|
19817
|
+
if (path22.dirname(dir) === dir)
|
|
18762
19818
|
return null;
|
|
18763
19819
|
}
|
|
18764
19820
|
}
|
|
18765
19821
|
var projectDirs = (cwd) => {
|
|
18766
19822
|
const top = gitTop(cwd);
|
|
18767
19823
|
const dirs = [];
|
|
18768
|
-
for (let dir =
|
|
19824
|
+
for (let dir = path22.resolve(cwd);; dir = path22.dirname(dir)) {
|
|
18769
19825
|
dirs.push(dir);
|
|
18770
|
-
if (!top || dir === top ||
|
|
19826
|
+
if (!top || dir === top || path22.dirname(dir) === dir)
|
|
18771
19827
|
return dirs;
|
|
18772
19828
|
}
|
|
18773
19829
|
};
|
|
@@ -18779,12 +19835,12 @@ var CLAUDE_MANAGED = {
|
|
|
18779
19835
|
};
|
|
18780
19836
|
function claudeSettingsFiles(cwd, env, platform = process.platform) {
|
|
18781
19837
|
const project = env.CLAUDE_PROJECT_DIR || gitTop(cwd) || cwd;
|
|
18782
|
-
const user = env.CLAUDE_CONFIG_DIR ||
|
|
19838
|
+
const user = env.CLAUDE_CONFIG_DIR || path22.join(homeOf(env), ".claude");
|
|
18783
19839
|
return [
|
|
18784
19840
|
...CLAUDE_MANAGED[platform] ? [CLAUDE_MANAGED[platform]] : [],
|
|
18785
|
-
|
|
18786
|
-
|
|
18787
|
-
|
|
19841
|
+
path22.join(user, "settings.json"),
|
|
19842
|
+
path22.join(project, ".claude", "settings.json"),
|
|
19843
|
+
path22.join(project, ".claude", "settings.local.json")
|
|
18788
19844
|
];
|
|
18789
19845
|
}
|
|
18790
19846
|
var bashPattern = (entry) => {
|
|
@@ -18874,10 +19930,10 @@ var opencodeConfig = (value, rules) => {
|
|
|
18874
19930
|
function opencodeRules(cwd, env = process.env) {
|
|
18875
19931
|
const rules = EMPTY2();
|
|
18876
19932
|
try {
|
|
18877
|
-
const config = env.XDG_CONFIG_HOME ||
|
|
19933
|
+
const config = env.XDG_CONFIG_HOME || path22.join(homeOf(env), ".config");
|
|
18878
19934
|
const files = [
|
|
18879
|
-
...OPENCODE_NAMES.map((name) =>
|
|
18880
|
-
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [
|
|
19935
|
+
...OPENCODE_NAMES.map((name) => path22.join(config, "opencode", name)),
|
|
19936
|
+
...projectDirs(cwd).flatMap((dir) => OPENCODE_NAMES.flatMap((name) => [path22.join(dir, name), path22.join(dir, ".opencode", name)])),
|
|
18881
19937
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []
|
|
18882
19938
|
];
|
|
18883
19939
|
for (const file of files) {
|
|
@@ -18921,9 +19977,9 @@ var codexRule = (body) => {
|
|
|
18921
19977
|
function codexRules(cwd, env = process.env) {
|
|
18922
19978
|
const rules = EMPTY2();
|
|
18923
19979
|
try {
|
|
18924
|
-
const home = env.CODEX_HOME ||
|
|
19980
|
+
const home = env.CODEX_HOME || path22.join(homeOf(env), ".codex");
|
|
18925
19981
|
const top = gitTop(cwd) ?? cwd;
|
|
18926
|
-
for (const dir of [
|
|
19982
|
+
for (const dir of [path22.join(home, "rules"), path22.join(top, ".codex", "rules")]) {
|
|
18927
19983
|
let files;
|
|
18928
19984
|
try {
|
|
18929
19985
|
files = fs16.readdirSync(dir).filter((name) => name.endsWith(".rules"));
|
|
@@ -18933,7 +19989,7 @@ function codexRules(cwd, env = process.env) {
|
|
|
18933
19989
|
return "unreadable";
|
|
18934
19990
|
}
|
|
18935
19991
|
for (const name of files) {
|
|
18936
|
-
const text = readText2(
|
|
19992
|
+
const text = readText2(path22.join(dir, name)) ?? "";
|
|
18937
19993
|
for (const part of text.split(/\bprefix_rule\s*\(/u).slice(1)) {
|
|
18938
19994
|
const rule = codexRule(part);
|
|
18939
19995
|
if (!rule)
|
|
@@ -18952,8 +20008,8 @@ function cursorRules(cwd, env = process.env) {
|
|
|
18952
20008
|
try {
|
|
18953
20009
|
const top = gitTop(cwd) ?? cwd;
|
|
18954
20010
|
for (const file of [
|
|
18955
|
-
|
|
18956
|
-
|
|
20011
|
+
path22.join(homeOf(env), ".cursor", "cli-config.json"),
|
|
20012
|
+
path22.join(top, ".cursor", "cli.json")
|
|
18957
20013
|
]) {
|
|
18958
20014
|
const text = readText2(file);
|
|
18959
20015
|
if (text === null)
|
|
@@ -19029,21 +20085,21 @@ function wrapAllowed(read, words, command, options = {}) {
|
|
|
19029
20085
|
// packages/workit-core/src/core/heavy.ts
|
|
19030
20086
|
import fs17 from "node:fs";
|
|
19031
20087
|
import os7 from "node:os";
|
|
19032
|
-
import
|
|
20088
|
+
import path23 from "node:path";
|
|
19033
20089
|
function stateDir(env, platform = process.platform) {
|
|
19034
20090
|
if (env.WORKFLOW_TOOLKIT_STATE)
|
|
19035
20091
|
return env.WORKFLOW_TOOLKIT_STATE;
|
|
19036
20092
|
if (env.XDG_STATE_HOME)
|
|
19037
|
-
return
|
|
20093
|
+
return path23.join(env.XDG_STATE_HOME, "workit");
|
|
19038
20094
|
const home = env.HOME || os7.homedir();
|
|
19039
20095
|
if (platform === "darwin")
|
|
19040
|
-
return
|
|
20096
|
+
return path23.join(home, "Library", "Application Support", "workit");
|
|
19041
20097
|
if (platform === "win32")
|
|
19042
|
-
return
|
|
19043
|
-
return
|
|
20098
|
+
return path23.join(env.LOCALAPPDATA ?? path23.join(home, "AppData", "Local"), "workit");
|
|
20099
|
+
return path23.join(home, ".local", "state", "workit");
|
|
19044
20100
|
}
|
|
19045
20101
|
var HEAVY_COMMANDS_FILE = "heavy-commands.json";
|
|
19046
|
-
var heavyCommandsFile = (env) =>
|
|
20102
|
+
var heavyCommandsFile = (env) => path23.join(stateDir(env), HEAVY_COMMANDS_FILE);
|
|
19047
20103
|
var HEAVY_SCRIPT = /^(?:test|tests|t|tst|unit|integration|e2e|coverage|cov|lint|eslint|typecheck|type-check|types|tsc|check|verify|validate|build|compile|bundle|ci|sonar|cypress|playwright|jest|vitest|mocha|bench|benchmark|all)(?:$|[:_.-])/iu;
|
|
19048
20104
|
var INFO_FLAGS = new Set(["--version", "-v", "-V", "--help", "-h", "help", "version"]);
|
|
19049
20105
|
var WATCH_FLAG = /^(?:--watch(?:All)?(?:=.*)?|-w|--serve)$/u;
|
|
@@ -19318,7 +20374,7 @@ function machinePatterns(env) {
|
|
|
19318
20374
|
return isRecord4(value) ? stringList(value.patterns) : [];
|
|
19319
20375
|
}
|
|
19320
20376
|
function repoPatterns(cwd) {
|
|
19321
|
-
return repoHeavyCommands(gitTop(cwd) ??
|
|
20377
|
+
return repoHeavyCommands(gitTop(cwd) ?? path23.resolve(cwd));
|
|
19322
20378
|
}
|
|
19323
20379
|
function heavyLines(lines, options) {
|
|
19324
20380
|
const found = [];
|
|
@@ -19396,12 +20452,12 @@ function heavyPlan(command, options) {
|
|
|
19396
20452
|
}
|
|
19397
20453
|
function workitOnPath(env = process.env) {
|
|
19398
20454
|
const names = process.platform === "win32" ? ["workit.exe", "workit.cmd", "workit"] : ["workit"];
|
|
19399
|
-
for (const dir of (env.PATH ?? "").split(
|
|
20455
|
+
for (const dir of (env.PATH ?? "").split(path23.delimiter))
|
|
19400
20456
|
for (const name of names) {
|
|
19401
20457
|
if (!dir)
|
|
19402
20458
|
continue;
|
|
19403
20459
|
try {
|
|
19404
|
-
fs17.accessSync(
|
|
20460
|
+
fs17.accessSync(path23.join(dir, name), fs17.constants.X_OK);
|
|
19405
20461
|
return true;
|
|
19406
20462
|
} catch {}
|
|
19407
20463
|
}
|
|
@@ -19461,16 +20517,16 @@ var splitDenial = (plan) => ({
|
|
|
19461
20517
|
unblock: plan.unblock
|
|
19462
20518
|
});
|
|
19463
20519
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
19464
|
-
import { existsSync as
|
|
20520
|
+
import { existsSync as existsSync8, realpathSync as realpathSync5 } from "node:fs";
|
|
19465
20521
|
import os9 from "node:os";
|
|
19466
|
-
import
|
|
20522
|
+
import path25 from "node:path";
|
|
19467
20523
|
|
|
19468
20524
|
// packages/workit-core/src/core/live-session.ts
|
|
19469
20525
|
import { spawnSync as spawnSync12 } from "node:child_process";
|
|
19470
20526
|
import { createHash as createHash15 } from "node:crypto";
|
|
19471
20527
|
import fs18 from "node:fs";
|
|
19472
20528
|
import os8 from "node:os";
|
|
19473
|
-
import
|
|
20529
|
+
import path24 from "node:path";
|
|
19474
20530
|
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
19475
20531
|
var HEARTBEAT_EVERY_MS = 30000;
|
|
19476
20532
|
var liveProbe = {
|
|
@@ -19544,15 +20600,15 @@ var liveOwner = (input, transport, env = process.env) => {
|
|
|
19544
20600
|
};
|
|
19545
20601
|
};
|
|
19546
20602
|
var checkoutOf = (target) => {
|
|
19547
|
-
let dir =
|
|
19548
|
-
while (!fs18.existsSync(dir) &&
|
|
19549
|
-
dir =
|
|
20603
|
+
let dir = path24.resolve(target);
|
|
20604
|
+
while (!fs18.existsSync(dir) && path24.dirname(dir) !== dir)
|
|
20605
|
+
dir = path24.dirname(dir);
|
|
19550
20606
|
if (fs18.existsSync(dir) && !fs18.statSync(dir).isDirectory())
|
|
19551
|
-
dir =
|
|
19552
|
-
const parts = dir.split(
|
|
20607
|
+
dir = path24.dirname(dir);
|
|
20608
|
+
const parts = dir.split(path24.sep);
|
|
19553
20609
|
const dotGit = parts.lastIndexOf(".git");
|
|
19554
20610
|
if (dotGit > 0)
|
|
19555
|
-
dir = parts.slice(0, dotGit).join(
|
|
20611
|
+
dir = parts.slice(0, dotGit).join(path24.sep) || path24.sep;
|
|
19556
20612
|
const location = resolveStore(dir);
|
|
19557
20613
|
if (location instanceof Error || !location.shared || !location.top)
|
|
19558
20614
|
return null;
|
|
@@ -19562,9 +20618,9 @@ var checkoutOf = (target) => {
|
|
|
19562
20618
|
} catch {}
|
|
19563
20619
|
return top;
|
|
19564
20620
|
};
|
|
19565
|
-
var markerFile2 = (top) =>
|
|
20621
|
+
var markerFile2 = (top) => path24.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
19566
20622
|
var checkoutKey = (top, platform = process.platform) => {
|
|
19567
|
-
let key =
|
|
20623
|
+
let key = path24.resolve(top);
|
|
19568
20624
|
try {
|
|
19569
20625
|
key = fs18.realpathSync.native(key);
|
|
19570
20626
|
} catch {}
|
|
@@ -19659,7 +20715,7 @@ var claimLiveSession = (target, owner, deps = {}) => {
|
|
|
19659
20715
|
checkout,
|
|
19660
20716
|
heartbeatAt: now.toISOString()
|
|
19661
20717
|
};
|
|
19662
|
-
fs18.mkdirSync(
|
|
20718
|
+
fs18.mkdirSync(path24.dirname(file), { recursive: true });
|
|
19663
20719
|
const temp = `${file}.${process.pid}.tmp`;
|
|
19664
20720
|
fs18.writeFileSync(temp, JSON.stringify(marker));
|
|
19665
20721
|
fs18.renameSync(temp, file);
|
|
@@ -19671,22 +20727,22 @@ var NONE3 = { kind: "none" };
|
|
|
19671
20727
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
19672
20728
|
var canonical2 = (file) => {
|
|
19673
20729
|
let rest = "";
|
|
19674
|
-
for (let dir =
|
|
20730
|
+
for (let dir = path25.resolve(file);; dir = path25.dirname(dir)) {
|
|
19675
20731
|
try {
|
|
19676
|
-
return
|
|
20732
|
+
return path25.join(realpathSync5.native(dir), rest);
|
|
19677
20733
|
} catch {
|
|
19678
|
-
if (
|
|
19679
|
-
return
|
|
19680
|
-
rest = rest ?
|
|
20734
|
+
if (path25.dirname(dir) === dir)
|
|
20735
|
+
return path25.resolve(file);
|
|
20736
|
+
rest = rest ? path25.join(path25.basename(dir), rest) : path25.basename(dir);
|
|
19681
20737
|
}
|
|
19682
20738
|
}
|
|
19683
20739
|
};
|
|
19684
20740
|
var exempt = (cwd, root, file, plans) => {
|
|
19685
20741
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
19686
|
-
const relative =
|
|
19687
|
-
if (relative.startsWith("..") ||
|
|
20742
|
+
const relative = path25.relative(fold(canonical2(root)), fold(canonical2(path25.resolve(cwd, file))));
|
|
20743
|
+
if (relative.startsWith("..") || path25.isAbsolute(relative))
|
|
19688
20744
|
return true;
|
|
19689
|
-
const posix = relative.split(
|
|
20745
|
+
const posix = relative.split(path25.sep).join("/");
|
|
19690
20746
|
const segments = posix.split("/");
|
|
19691
20747
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
19692
20748
|
};
|
|
@@ -19863,10 +20919,10 @@ var linkSources = (args, at) => {
|
|
|
19863
20919
|
const sources = names.filter((name, index) => into >= 0 ? name !== dest : index < names.length - 1 || names.length === 1);
|
|
19864
20920
|
const target = dest === undefined ? null : at(dest);
|
|
19865
20921
|
const symbolic = args.some((arg) => /^-[a-zA-Z]*s|^--symbolic$/.test(arg));
|
|
19866
|
-
const base = target === null ? null : into >= 0 || sources.length > 1 ||
|
|
20922
|
+
const base = target === null ? null : into >= 0 || sources.length > 1 || existsSync8(target) || /[\\/]$/.test(dest ?? "") ? target : path25.dirname(target);
|
|
19867
20923
|
return sources.flatMap((source) => {
|
|
19868
|
-
if (symbolic && base !== null && !
|
|
19869
|
-
return [
|
|
20924
|
+
if (symbolic && base !== null && !path25.isAbsolute(homeExpanded(source)))
|
|
20925
|
+
return [path25.resolve(base, source)];
|
|
19870
20926
|
const resolved = at(source);
|
|
19871
20927
|
return resolved ? [resolved] : [];
|
|
19872
20928
|
});
|
|
@@ -19895,7 +20951,7 @@ var shellWrites = (command, depth = 0) => {
|
|
|
19895
20951
|
return { writes: true, targets: unknown ? null : targets };
|
|
19896
20952
|
};
|
|
19897
20953
|
var cache2 = new Map;
|
|
19898
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
20954
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path25.resolve(root, file)) ?? "-").join("\x00");
|
|
19899
20955
|
var gateFor = (cwd) => {
|
|
19900
20956
|
const store = new TaskStore(cwd);
|
|
19901
20957
|
const listed = store.listTaskIndex();
|
|
@@ -19974,13 +21030,13 @@ var gitMutationDirs = (cwd, command, dialect) => {
|
|
|
19974
21030
|
return [];
|
|
19975
21031
|
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
19976
21032
|
return [];
|
|
19977
|
-
return [
|
|
21033
|
+
return [path25.resolve(cwd, invocation.dir ?? ".")];
|
|
19978
21034
|
});
|
|
19979
21035
|
};
|
|
19980
21036
|
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
19981
21037
|
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
19982
21038
|
var POPD2 = new Set(["popd", "pop-location"]);
|
|
19983
|
-
var UNKNOWN_DIR =
|
|
21039
|
+
var UNKNOWN_DIR = path25.resolve(path25.parse(process.cwd()).root, ".workit-unknown-dir");
|
|
19984
21040
|
var cdInto = (dir, words, strict = false) => {
|
|
19985
21041
|
const lost = strict ? UNKNOWN_DIR : undefined;
|
|
19986
21042
|
const named = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
@@ -19989,7 +21045,7 @@ var cdInto = (dir, words, strict = false) => {
|
|
|
19989
21045
|
return lost;
|
|
19990
21046
|
if (dir === undefined)
|
|
19991
21047
|
return lost;
|
|
19992
|
-
return target === undefined ? os9.homedir() :
|
|
21048
|
+
return target === undefined ? os9.homedir() : path25.resolve(dir, target);
|
|
19993
21049
|
};
|
|
19994
21050
|
var quoteWord2 = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
19995
21051
|
var homeExpanded = (file) => {
|
|
@@ -20003,7 +21059,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20003
21059
|
const groups = [];
|
|
20004
21060
|
const at = (file) => {
|
|
20005
21061
|
const home = homeExpanded(file);
|
|
20006
|
-
return
|
|
21062
|
+
return path25.isAbsolute(home) ? home : dir === undefined ? null : path25.resolve(dir, home);
|
|
20007
21063
|
};
|
|
20008
21064
|
for (const segment of segmentsOf(command, dialect)) {
|
|
20009
21065
|
if (segment.group === "open") {
|
|
@@ -20034,7 +21090,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20034
21090
|
}
|
|
20035
21091
|
const inner = depth < 4 ? wrappedScript(words) ?? (strict ? shellScript2(words) : null) : null;
|
|
20036
21092
|
if (inner !== null && dir !== undefined)
|
|
20037
|
-
targets.push(...segmentTargets(dir, inner, "posix", depth + 1, strict));
|
|
21093
|
+
targets.push(...segmentTargets(workitRunDir(dir, words), inner, "posix", depth + 1, strict));
|
|
20038
21094
|
if (strict && dir !== undefined) {
|
|
20039
21095
|
const runIn = dir;
|
|
20040
21096
|
for (const sub of depth < 4 ? segment.subs ?? [] : [])
|
|
@@ -20043,7 +21099,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20043
21099
|
for (const file of opaque ?? []) {
|
|
20044
21100
|
const resolved = at(file);
|
|
20045
21101
|
if (resolved)
|
|
20046
|
-
targets.push(file === "." ?
|
|
21102
|
+
targets.push(file === "." ? path25.join(resolved, DIR_TARGET) : resolved);
|
|
20047
21103
|
}
|
|
20048
21104
|
const ln = words.findIndex((word) => /^ln$/i.test(word.split(/[\\/]/).at(-1) ?? ""));
|
|
20049
21105
|
if (ln >= 0 && words.slice(0, ln).every((word) => PREFIXES2.has(word)))
|
|
@@ -20058,7 +21114,7 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20058
21114
|
const edits = editsOf(segment.words);
|
|
20059
21115
|
if (edits === null) {
|
|
20060
21116
|
if (dir !== undefined)
|
|
20061
|
-
targets.push(
|
|
21117
|
+
targets.push(path25.join(dir, DIR_TARGET));
|
|
20062
21118
|
} else
|
|
20063
21119
|
for (const file of edits) {
|
|
20064
21120
|
const resolved = at(file);
|
|
@@ -20069,20 +21125,28 @@ var segmentTargets = (cwd, command, dialect, depth = 0, strict = false) => {
|
|
|
20069
21125
|
for (const invocation of workitInvocations(segment.words.map(quoteWord2).join(" "), "posix")) {
|
|
20070
21126
|
const { at: runsIn, args } = workitCwd(dir, invocation);
|
|
20071
21127
|
if (args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), ""))
|
|
20072
|
-
targets.push(
|
|
21128
|
+
targets.push(path25.join(runsIn, DIR_TARGET));
|
|
20073
21129
|
}
|
|
20074
21130
|
}
|
|
20075
21131
|
return targets;
|
|
20076
21132
|
};
|
|
21133
|
+
var workitRunDir = (dir, words) => {
|
|
21134
|
+
for (const invocation of workitInvocations(words.map(quoteWord2).join(" "), "posix")) {
|
|
21135
|
+
const { at, args } = workitCwd(dir, invocation);
|
|
21136
|
+
if (args[0] === "check" || args[0] === "lock" && args[1] === "run")
|
|
21137
|
+
return at;
|
|
21138
|
+
}
|
|
21139
|
+
return dir;
|
|
21140
|
+
};
|
|
20077
21141
|
var workitCwd = (dir, args) => {
|
|
20078
21142
|
const { rest, cwd } = skipWorkitGlobals(args);
|
|
20079
|
-
return { at: cwd === null ? dir :
|
|
21143
|
+
return { at: cwd === null ? dir : path25.resolve(dir, homeExpanded(cwd)), args: rest };
|
|
20080
21144
|
};
|
|
20081
21145
|
var shellMutationTargets = (cwd, command, dialect = "posix", strict = false) => {
|
|
20082
21146
|
try {
|
|
20083
21147
|
return [
|
|
20084
21148
|
...segmentTargets(cwd, command, dialect, 0, strict),
|
|
20085
|
-
...gitMutationDirs(cwd, command, dialect).map((dir) =>
|
|
21149
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path25.join(dir, DIR_TARGET))
|
|
20086
21150
|
];
|
|
20087
21151
|
} catch {
|
|
20088
21152
|
return [];
|
|
@@ -20119,7 +21183,7 @@ var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
|
20119
21183
|
var guarded = (targets) => {
|
|
20120
21184
|
const seen = new Set;
|
|
20121
21185
|
return targets.filter((target) => {
|
|
20122
|
-
const dir =
|
|
21186
|
+
const dir = path25.dirname(target);
|
|
20123
21187
|
if (seen.has(dir))
|
|
20124
21188
|
return false;
|
|
20125
21189
|
seen.add(dir);
|
|
@@ -20140,7 +21204,7 @@ var claimAll = (owner, targets, deps) => {
|
|
|
20140
21204
|
for (const target of guarded(targets))
|
|
20141
21205
|
claimLiveSession(target, owner, deps);
|
|
20142
21206
|
};
|
|
20143
|
-
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) =>
|
|
21207
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path25.resolve(cwd, file)) : [path25.join(cwd, DIR_TARGET)];
|
|
20144
21208
|
var issuedTarget = (cwd, targets) => {
|
|
20145
21209
|
const state = resolveStateDir();
|
|
20146
21210
|
const found = targets.find((target) => underIssued(state, cwd, homeExpanded(target), process.platform, canonical2));
|
|
@@ -20148,7 +21212,7 @@ var issuedTarget = (cwd, targets) => {
|
|
|
20148
21212
|
};
|
|
20149
21213
|
var underIssued = (state, cwd, target, platform = process.platform, real = (file) => file) => {
|
|
20150
21214
|
const win = platform === "win32";
|
|
20151
|
-
const p = win ?
|
|
21215
|
+
const p = win ? path25.win32 : path25.posix;
|
|
20152
21216
|
const fold = (value) => win || platform === "darwin" ? value.toLowerCase() : value;
|
|
20153
21217
|
const named = win ? target.replace(/^\/([a-zA-Z])(?=\/|$)/, "$1:") : target;
|
|
20154
21218
|
const relative = p.relative(fold(real(p.resolve(state))), fold(real(p.resolve(cwd, named))));
|
|
@@ -20239,6 +21303,24 @@ function beforeWriteGate(cwd, paths) {
|
|
|
20239
21303
|
}
|
|
20240
21304
|
// packages/workit-core/src/hooks/stop.ts
|
|
20241
21305
|
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
21306
|
+
|
|
21307
|
+
// packages/workit-core/src/core/policy/current-judgment.ts
|
|
21308
|
+
function currentJudgment(cwd) {
|
|
21309
|
+
try {
|
|
21310
|
+
const store = new TaskStore(cwd);
|
|
21311
|
+
const listed = store.listTaskIndex();
|
|
21312
|
+
const key = store.currentKey();
|
|
21313
|
+
if (!listed.ok || !key.ok)
|
|
21314
|
+
return null;
|
|
21315
|
+
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
21316
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
21317
|
+
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
21318
|
+
} catch {
|
|
21319
|
+
return null;
|
|
21320
|
+
}
|
|
21321
|
+
}
|
|
21322
|
+
|
|
21323
|
+
// packages/workit-core/src/hooks/stop.ts
|
|
20242
21324
|
var NONE4 = { kind: "none" };
|
|
20243
21325
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20244
21326
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -20331,29 +21413,32 @@ var redChecks = (rows, pr, head, endpoint) => {
|
|
|
20331
21413
|
reason: `PR #${pr}'s checks were last seen ${state} at ${short(head)}, and this workspace's endpoint is \`${endpoint}\`: run \`workit ci wait --pr ${pr}\`${state === "failing" ? " after fixing the failures `workit pr status` lists (then push)" : ""}.`
|
|
20332
21414
|
};
|
|
20333
21415
|
};
|
|
20334
|
-
var
|
|
20335
|
-
|
|
20336
|
-
|
|
20337
|
-
const listed = store.listTaskIndex();
|
|
20338
|
-
const key = store.currentKey();
|
|
20339
|
-
if (!listed.ok || !key.ok)
|
|
20340
|
-
return null;
|
|
20341
|
-
const entry = listed.data.filter((item) => item.key === key.data.key && item.status !== "closed").toSorted((left, right) => right.updatedAt.localeCompare(left.updatedAt))[0];
|
|
20342
|
-
const task = entry ? store.readTask(entry.id) : null;
|
|
20343
|
-
return task?.ok === true ? latestJudgment(task.data) : null;
|
|
20344
|
-
} catch {
|
|
20345
|
-
return null;
|
|
20346
|
-
}
|
|
21416
|
+
var prBase = (rows) => {
|
|
21417
|
+
const seen = rows.findLast((row) => (row.type === "pr.status" || row.type === "pr.created") && row.observer === "workit_cli" && typeof row.base === "string");
|
|
21418
|
+
return typeof seen?.base === "string" ? seen.base : null;
|
|
20347
21419
|
};
|
|
20348
|
-
var unverified = (cwd, branch, all, descriptor, verification) => {
|
|
21420
|
+
var unverified = (cwd, branch, all, descriptor, verification, head) => {
|
|
20349
21421
|
const check = checkVerdicts(cwd, branch, all);
|
|
20350
21422
|
if (check.accepted.accepted || check.accepted.reasons.includes("failing_verdict"))
|
|
20351
21423
|
return null;
|
|
20352
|
-
const judgment =
|
|
21424
|
+
const judgment = currentJudgment(cwd);
|
|
20353
21425
|
const high = judgment?.riskTier === "high";
|
|
20354
|
-
|
|
20355
|
-
|
|
20356
|
-
|
|
21426
|
+
let independent;
|
|
21427
|
+
if (verification === "proportional") {
|
|
21428
|
+
const lane = branchLane(cwd, {
|
|
21429
|
+
base: prBase(all.filter((row) => row.branch === branch)),
|
|
21430
|
+
head,
|
|
21431
|
+
judgment
|
|
21432
|
+
});
|
|
21433
|
+
const need = laneNeed(lane.lane);
|
|
21434
|
+
if (need === "none")
|
|
21435
|
+
return null;
|
|
21436
|
+
independent = need === "non-author";
|
|
21437
|
+
} else {
|
|
21438
|
+
if (judgment && !judgment.behaviorChange && !high)
|
|
21439
|
+
return null;
|
|
21440
|
+
independent = verification === "independent" || high;
|
|
21441
|
+
}
|
|
20357
21442
|
if (!independent && check.review === "self-reviewed")
|
|
20358
21443
|
return null;
|
|
20359
21444
|
const has = check.review === "self-reviewed" ? "only a self verdict" : "no current verdict";
|
|
@@ -20400,7 +21485,7 @@ function stopObligation(input, descriptor) {
|
|
|
20400
21485
|
}
|
|
20401
21486
|
if (!delivered || endpoint !== "green" && endpoint !== "merged")
|
|
20402
21487
|
return null;
|
|
20403
|
-
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification);
|
|
21488
|
+
const left = unverified(input.cwd, branch, all, descriptor, autonomy.verification, head);
|
|
20404
21489
|
return left ? { ...left, head } : null;
|
|
20405
21490
|
} catch {
|
|
20406
21491
|
return null;
|
|
@@ -20661,13 +21746,13 @@ ${line}` };
|
|
|
20661
21746
|
return decision;
|
|
20662
21747
|
};
|
|
20663
21748
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20664
|
-
import { existsSync as
|
|
20665
|
-
import
|
|
21749
|
+
import { existsSync as existsSync9, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
21750
|
+
import path26 from "node:path";
|
|
20666
21751
|
var isRecord5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20667
21752
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20668
21753
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
20669
21754
|
var existingDirectory = (value) => {
|
|
20670
|
-
if (!nonEmpty2(value) || !
|
|
21755
|
+
if (!nonEmpty2(value) || !path26.isAbsolute(value) || !existsSync9(value))
|
|
20671
21756
|
return null;
|
|
20672
21757
|
try {
|
|
20673
21758
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20735,9 +21820,9 @@ var writeTargets = (input) => {
|
|
|
20735
21820
|
};
|
|
20736
21821
|
// packages/workit-core/src/hooks/standing.ts
|
|
20737
21822
|
import fs19 from "node:fs";
|
|
20738
|
-
import
|
|
21823
|
+
import path27 from "node:path";
|
|
20739
21824
|
var readPlans = (store) => {
|
|
20740
|
-
const dir =
|
|
21825
|
+
const dir = path27.join(store, "fanouts");
|
|
20741
21826
|
let names;
|
|
20742
21827
|
try {
|
|
20743
21828
|
names = fs19.readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
@@ -20747,7 +21832,7 @@ var readPlans = (store) => {
|
|
|
20747
21832
|
const plans = [];
|
|
20748
21833
|
for (const name of names) {
|
|
20749
21834
|
try {
|
|
20750
|
-
const raw = JSON.parse(fs19.readFileSync(
|
|
21835
|
+
const raw = JSON.parse(fs19.readFileSync(path27.join(dir, name), "utf8"));
|
|
20751
21836
|
if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
|
|
20752
21837
|
continue;
|
|
20753
21838
|
const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
|
|
@@ -20837,8 +21922,13 @@ var standingDelta = (input) => {
|
|
|
20837
21922
|
};
|
|
20838
21923
|
var sinceStart = new Map;
|
|
20839
21924
|
// packages/workit-core/src/hooks/cwd-normalize.ts
|
|
20840
|
-
var
|
|
21925
|
+
var DIR = String.raw`[A-Za-z0-9_./@%+:,=-]+|'[^'$${"`"}\\\n]*'|"[^"$${"`"}\\!\n]*"`;
|
|
21926
|
+
var COMPOUND = new RegExp(String.raw`^\s*cd\s+(${DIR})\s*&&\s*(workit(?:\s[\s\S]*)?)$`, "u");
|
|
21927
|
+
var GLOBALS_FIRST = new RegExp(String.raw`^(\s*workit)((?:\s+(?:--json|--cwd(?:=|\s+)(?:${DIR})))+)\s+([a-z][a-z-]*)(?=\s|$)`, "u");
|
|
21928
|
+
var VERB_FIRST = /^((?:--json\s+)*)([a-z][a-z-]*)(?=\s|$)/u;
|
|
21929
|
+
var VERBS = new Set(WORKIT_ALLOWED_VERBS);
|
|
20841
21930
|
var FILTERS = new Set(["tail", "head", "grep"]);
|
|
21931
|
+
var clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
20842
21932
|
var stdinOnly = (words) => {
|
|
20843
21933
|
const [name, ...args] = words;
|
|
20844
21934
|
const operands = args.filter((w) => !w.startsWith("-"));
|
|
@@ -20860,53 +21950,67 @@ var cwdRewrite = (command) => {
|
|
|
20860
21950
|
return null;
|
|
20861
21951
|
if (head.words.some((w) => w === "--cwd" || w.startsWith("--cwd=")))
|
|
20862
21952
|
return null;
|
|
20863
|
-
const clean = (s) => !s.unanalyzable && !s.group && !s.subs?.length && !s.stdin?.length && !s.redirects.length;
|
|
20864
21953
|
if (!segments.every(clean))
|
|
20865
21954
|
return null;
|
|
20866
21955
|
if (!segments.slice(0, -1).every((s) => s.pipe))
|
|
20867
21956
|
return null;
|
|
20868
21957
|
if (!filters.every((s) => FILTERS.has(s.words[0] ?? "") && stdinOnly(s.words)))
|
|
20869
21958
|
return null;
|
|
20870
|
-
|
|
21959
|
+
const args = rest.slice("workit".length).trimStart();
|
|
21960
|
+
const verb = VERB_FIRST.exec(args);
|
|
21961
|
+
if (!verb?.[2] || !VERBS.has(verb[2]))
|
|
21962
|
+
return `workit --cwd ${dir} ${args}`.trimEnd();
|
|
21963
|
+
const json = verb[1] ? " --json" : "";
|
|
21964
|
+
return `workit ${verb[2]} --cwd ${dir}${json}${args.slice(verb[0].length)}`.trimEnd();
|
|
21965
|
+
};
|
|
21966
|
+
var verbFirst = (command) => {
|
|
21967
|
+
const match = GLOBALS_FIRST.exec(command);
|
|
21968
|
+
const [whole, lead, globals, verb] = match ?? [];
|
|
21969
|
+
if (!whole || !verb || !VERBS.has(verb) || !globals?.includes("--cwd"))
|
|
21970
|
+
return null;
|
|
21971
|
+
const segments = segmentsOf(command);
|
|
21972
|
+
if (segments.length !== 1 || !segments.every(clean))
|
|
21973
|
+
return null;
|
|
21974
|
+
return `${lead} ${verb}${globals}${command.slice(whole.length)}`;
|
|
20871
21975
|
};
|
|
20872
21976
|
|
|
20873
21977
|
// packages/workit-core/src/hooks/worktree-confine.ts
|
|
20874
21978
|
import { createHash as createHash16 } from "node:crypto";
|
|
20875
21979
|
import fs20 from "node:fs";
|
|
20876
|
-
import
|
|
21980
|
+
import path28 from "node:path";
|
|
20877
21981
|
var FANOUT_TOKEN_FILE = "workit-fanout-token";
|
|
20878
21982
|
var NONE5 = { kind: "none" };
|
|
20879
21983
|
var CASE_INSENSITIVE2 = process.platform === "darwin" || process.platform === "win32";
|
|
20880
21984
|
var canonical3 = (file) => {
|
|
20881
21985
|
let rest = "";
|
|
20882
|
-
for (let dir =
|
|
21986
|
+
for (let dir = path28.resolve(file);; dir = path28.dirname(dir)) {
|
|
20883
21987
|
try {
|
|
20884
|
-
return
|
|
21988
|
+
return path28.join(fs20.realpathSync.native(dir), rest);
|
|
20885
21989
|
} catch {
|
|
20886
|
-
if (
|
|
20887
|
-
return
|
|
20888
|
-
rest = rest ?
|
|
21990
|
+
if (path28.dirname(dir) === dir)
|
|
21991
|
+
return path28.resolve(file);
|
|
21992
|
+
rest = rest ? path28.join(path28.basename(dir), rest) : path28.basename(dir);
|
|
20889
21993
|
}
|
|
20890
21994
|
}
|
|
20891
21995
|
};
|
|
20892
21996
|
var within = (child, parent) => {
|
|
20893
21997
|
const fold = (value) => CASE_INSENSITIVE2 ? value.toLowerCase() : value;
|
|
20894
|
-
const relative =
|
|
20895
|
-
return relative === "" || !relative.startsWith("..") && !
|
|
21998
|
+
const relative = path28.relative(fold(parent), fold(child));
|
|
21999
|
+
return relative === "" || !relative.startsWith("..") && !path28.isAbsolute(relative);
|
|
20896
22000
|
};
|
|
20897
22001
|
var checkoutOf2 = (file) => {
|
|
20898
|
-
for (let dir = canonical3(file);; dir =
|
|
20899
|
-
const dotGit =
|
|
22002
|
+
for (let dir = canonical3(file);; dir = path28.dirname(dir)) {
|
|
22003
|
+
const dotGit = path28.join(dir, ".git");
|
|
20900
22004
|
if (fs20.existsSync(dotGit)) {
|
|
20901
22005
|
if (!fs20.statSync(dotGit).isFile())
|
|
20902
22006
|
return { root: dir, workit: false };
|
|
20903
22007
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
20904
22008
|
return {
|
|
20905
22009
|
root: dir,
|
|
20906
|
-
workit: Boolean(admin) && fs20.existsSync(
|
|
22010
|
+
workit: Boolean(admin) && fs20.existsSync(path28.join(path28.resolve(dir, admin), FANOUT_TOKEN_FILE))
|
|
20907
22011
|
};
|
|
20908
22012
|
}
|
|
20909
|
-
if (
|
|
22013
|
+
if (path28.dirname(dir) === dir)
|
|
20910
22014
|
return null;
|
|
20911
22015
|
}
|
|
20912
22016
|
};
|
|
@@ -20918,8 +22022,8 @@ var workitWorktreeOf = (file) => {
|
|
|
20918
22022
|
return null;
|
|
20919
22023
|
}
|
|
20920
22024
|
};
|
|
20921
|
-
var bindingDir = () =>
|
|
20922
|
-
var bindingFile = (session, agentId) =>
|
|
22025
|
+
var bindingDir = () => path28.join(resolveStateDir(), "worktree-bind");
|
|
22026
|
+
var bindingFile = (session, agentId) => path28.join(bindingDir(), `${createHash16("sha256").update(`${session}\x00${agentId}`).digest("hex").slice(0, 32)}.json`);
|
|
20923
22027
|
var readBinding = (file) => {
|
|
20924
22028
|
try {
|
|
20925
22029
|
const value = JSON.parse(fs20.readFileSync(file, "utf8"));
|
|
@@ -20944,7 +22048,7 @@ var bind = (input, worktree) => {
|
|
|
20944
22048
|
agentId,
|
|
20945
22049
|
at: new Date().toISOString()
|
|
20946
22050
|
};
|
|
20947
|
-
fs20.mkdirSync(
|
|
22051
|
+
fs20.mkdirSync(path28.dirname(file), { recursive: true });
|
|
20948
22052
|
fs20.writeFileSync(file, JSON.stringify(binding));
|
|
20949
22053
|
try {
|
|
20950
22054
|
if (inWorkitWorkspace(input.cwd))
|
|
@@ -20963,27 +22067,27 @@ var hostIsolated = (cwd, agentId) => {
|
|
|
20963
22067
|
const checkout = checkoutOf2(cwd);
|
|
20964
22068
|
if (!checkout)
|
|
20965
22069
|
return false;
|
|
20966
|
-
const dotGit =
|
|
22070
|
+
const dotGit = path28.join(checkout.root, ".git");
|
|
20967
22071
|
if (!fs20.statSync(dotGit).isFile())
|
|
20968
22072
|
return false;
|
|
20969
22073
|
const admin = /^gitdir:\s*(.+)$/mu.exec(fs20.readFileSync(dotGit, "utf8"))?.[1]?.trim();
|
|
20970
22074
|
if (!admin)
|
|
20971
22075
|
return false;
|
|
20972
|
-
const adminDir =
|
|
20973
|
-
const back = fs20.readFileSync(
|
|
20974
|
-
if (
|
|
22076
|
+
const adminDir = path28.resolve(checkout.root, admin);
|
|
22077
|
+
const back = fs20.readFileSync(path28.join(adminDir, "gitdir"), "utf8").trim();
|
|
22078
|
+
if (path28.relative(canonical3(path28.resolve(adminDir, back)), canonical3(dotGit)) !== "")
|
|
20975
22079
|
return false;
|
|
20976
|
-
const common =
|
|
20977
|
-
const gitDir = fs20.existsSync(common) ?
|
|
20978
|
-
const expected =
|
|
20979
|
-
return
|
|
22080
|
+
const common = path28.join(adminDir, "commondir");
|
|
22081
|
+
const gitDir = fs20.existsSync(common) ? path28.resolve(adminDir, fs20.readFileSync(common, "utf8").trim()) : adminDir;
|
|
22082
|
+
const expected = path28.join(path28.dirname(canonical3(gitDir)), ".claude", "worktrees", `agent-${agentId}`);
|
|
22083
|
+
return path28.relative(canonical3(expected), checkout.root) === "";
|
|
20980
22084
|
} catch {
|
|
20981
22085
|
return false;
|
|
20982
22086
|
}
|
|
20983
22087
|
};
|
|
20984
|
-
var shown = (target) =>
|
|
22088
|
+
var shown = (target) => path28.basename(target) === ".workit-live-target" ? path28.dirname(target) : target;
|
|
20985
22089
|
var unboundDeny = (input, target) => {
|
|
20986
|
-
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit --cwd <worktree>
|
|
22090
|
+
const unblock = "Work only in the worktree your brief names, which the lead made with `workit fanout worktree create <slice>`: edit files under it and run one verb per call against it (`workit git --cwd <worktree> commit -m … -- <paths>`, `workit check --cwd <worktree> <name>`). If your brief names no worktree, stop and report that to the lead.";
|
|
20987
22091
|
return {
|
|
20988
22092
|
kind: "deny",
|
|
20989
22093
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is not working in a Workit worktree yet, and ${shown(target)} is outside every worktree \`workit fanout worktree create\` made, so it would change the lead's checkout. ${unblock}`,
|
|
@@ -20991,7 +22095,7 @@ var unboundDeny = (input, target) => {
|
|
|
20991
22095
|
};
|
|
20992
22096
|
};
|
|
20993
22097
|
var outsideDeny = (input, worktree, target) => {
|
|
20994
|
-
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit --cwd ${worktree}
|
|
22098
|
+
const unblock = `Edit only under ${worktree} and run git there, one verb per call: \`workit git --cwd ${worktree} commit -m … -- <paths>\`, \`workit check --cwd ${worktree} <name>\` (or \`git -C ${worktree} …\`); a command whose writes Workit cannot see is judged by the directory it runs in. Anything outside it is a follow-up for your report.`;
|
|
20995
22099
|
return {
|
|
20996
22100
|
kind: "deny",
|
|
20997
22101
|
reason: `workit worktree confinement: implementer ${input.session.agentId} is bound to the worktree ${worktree}, and ${shown(target)} is outside it. ${unblock}`,
|
|
@@ -21000,7 +22104,7 @@ var outsideDeny = (input, worktree, target) => {
|
|
|
21000
22104
|
};
|
|
21001
22105
|
var unknownDirDeny = (input, worktree) => {
|
|
21002
22106
|
const wt = worktree ?? "<worktree>";
|
|
21003
|
-
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit --cwd ${wt}\`.`;
|
|
22107
|
+
const unblock = `Workit cannot resolve \`cd -\`, \`cd "$X"\` or \`popd\`: write by absolute path, \`git -C ${wt}\` or \`workit <verb> --cwd ${wt}\`.`;
|
|
21004
22108
|
return {
|
|
21005
22109
|
kind: "deny",
|
|
21006
22110
|
reason: `workit worktree confinement: implementer ${input.session.agentId} writes after an unknown directory change. ${unblock}`,
|
|
@@ -21011,7 +22115,7 @@ function confineImplementer(input, targets) {
|
|
|
21011
22115
|
try {
|
|
21012
22116
|
if (!input.session.agentId || hostIsolated(input.cwd, input.session.agentId))
|
|
21013
22117
|
return NONE5;
|
|
21014
|
-
if (targets.some((target) => within(
|
|
22118
|
+
if (targets.some((target) => within(path28.resolve(target), UNKNOWN_DIR)))
|
|
21015
22119
|
return unknownDirDeny(input, boundWorktree(input));
|
|
21016
22120
|
const bindings = canonical3(bindingDir());
|
|
21017
22121
|
const judged = targets.map((named) => ({ named, real: canonical3(named) })).filter(({ real }) => within(real, bindings) || checkoutOf2(real) !== null);
|
|
@@ -21158,7 +22262,7 @@ var subagentStartText = (input, descriptor, event) => {
|
|
|
21158
22262
|
return `${isolatedGit(input.cwd)}
|
|
21159
22263
|
${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
21160
22264
|
if (role === "implementer")
|
|
21161
|
-
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit --cwd <worktree>
|
|
22265
|
+
return `Workit observed ${who} as an implementer: it edits and commits only in its own git worktree, the one its brief names (the lead makes it with \`workit fanout worktree create <slice>\` before spawning), never in the lead's checkout, within its brief's scope. Workit binds it to the first Workit worktree it changes: from then on an edit or a mutating git command outside that worktree is denied, and before then so is one outside every Workit worktree. Run Workit against the worktree one verb per call, never \`cd … &&\` chains or \`$VAR\` paths: \`workit git --cwd <worktree> commit -m "<message>" -- <paths>\`, \`workit check --cwd <worktree> <name>\` (verb first, so host allow rules match). The worktree is on the brief's branch once the lead made it; otherwise switch with \`workit git --cwd <worktree> branch <branch> --base <base>\` (or \`--kind <kind> --slug <slug>\`, which picks the base); branch policy hooks still deny protected or non-compliant branches, raw git included.${guide ? ` ${guide}` : " (e.g. feature/<slug>)"} Never record a verdict on your own work. Never push, open a PR, or merge unless the brief asks for it.`;
|
|
21162
22266
|
const readOnly = `Workit observed ${who} as read-only/agent-guided.`;
|
|
21163
22267
|
if (role !== "judge" || !event.agentId)
|
|
21164
22268
|
return readOnly;
|
|
@@ -21175,7 +22279,7 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
21175
22279
|
var PRE_TOOL = new Set(["shell.pre", "write.pre", "tool.pre"]);
|
|
21176
22280
|
function handleHook(input, deps) {
|
|
21177
22281
|
const decision = decide(input, deps);
|
|
21178
|
-
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny")
|
|
22282
|
+
if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
|
|
21179
22283
|
return decision;
|
|
21180
22284
|
const delta = standingDelta(input);
|
|
21181
22285
|
if (!delta)
|
|
@@ -21242,9 +22346,12 @@ function decide(input, deps) {
|
|
|
21242
22346
|
if (plan.kind === "rewrite" && !rewrites && canDeny)
|
|
21243
22347
|
return heavyDenial(event.command, plan);
|
|
21244
22348
|
const lease = plan.kind === "rewrite" && rewrites ? plan : null;
|
|
21245
|
-
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) : null;
|
|
22349
|
+
const cwdCommand = !lease && rewrites && input.host === "claude_code" ? cwdRewrite(event.command) ?? verbFirst(event.command) : null;
|
|
21246
22350
|
const aside = plan.kind === "passthrough" ? plan.note : null;
|
|
21247
22351
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
22352
|
+
const bypass = !lease && input.host === "claude_code" ? workitBypassFlag(event.command, event.dialect) : null;
|
|
22353
|
+
if (bypass)
|
|
22354
|
+
return { kind: "ask", reason: bypass };
|
|
21248
22355
|
const rewrittenTo = lease?.command ?? cwdCommand;
|
|
21249
22356
|
const rewritten = (text) => rewrittenTo ? {
|
|
21250
22357
|
kind: "rewrite",
|
|
@@ -21331,7 +22438,7 @@ var dispatchHook = (adapter, raw, env = process.env) => {
|
|
|
21331
22438
|
};
|
|
21332
22439
|
// packages/workit-core/src/hooks/deadline.ts
|
|
21333
22440
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
21334
|
-
import
|
|
22441
|
+
import path29 from "node:path";
|
|
21335
22442
|
var HOOK_SKIP_TEXT = "workit context skipped: host under load";
|
|
21336
22443
|
var HOOK_DEADLINE_MS = 8000;
|
|
21337
22444
|
var HOST_DEADLINES = {
|
|
@@ -21349,7 +22456,7 @@ var hookDeadlineMs = (env, host = "", native) => {
|
|
|
21349
22456
|
const row = HOST_DEADLINES[host];
|
|
21350
22457
|
return override(env) ?? (row ? native === undefined ? Math.min(...Object.values(row)) : row[native] ?? row[""] : HOOK_DEADLINE_MS);
|
|
21351
22458
|
};
|
|
21352
|
-
var skipsFile = (stateDir) =>
|
|
22459
|
+
var skipsFile = (stateDir) => path29.join(stateDir, HOOK_SKIPS_FILE);
|
|
21353
22460
|
var recordHookSkip = (skip, stateDir = resolveStateDir()) => {
|
|
21354
22461
|
try {
|
|
21355
22462
|
mkdirSync3(stateDir, { recursive: true });
|
|
@@ -21671,6 +22778,17 @@ var render = (decision, native) => {
|
|
|
21671
22778
|
},
|
|
21672
22779
|
exitCode: 0
|
|
21673
22780
|
};
|
|
22781
|
+
if (decision.kind === "ask" && native === "PreToolUse")
|
|
22782
|
+
return {
|
|
22783
|
+
json: {
|
|
22784
|
+
hookSpecificOutput: {
|
|
22785
|
+
hookEventName: "PreToolUse",
|
|
22786
|
+
permissionDecision: "ask",
|
|
22787
|
+
permissionDecisionReason: decision.reason
|
|
22788
|
+
}
|
|
22789
|
+
},
|
|
22790
|
+
exitCode: 0
|
|
22791
|
+
};
|
|
21674
22792
|
if (decision.kind === "context" && native !== null && CONTEXT_EVENTS.has(native))
|
|
21675
22793
|
return {
|
|
21676
22794
|
json: { hookSpecificOutput: { hookEventName: native, additionalContext: decision.text } },
|
|
@@ -21994,7 +23112,7 @@ var codexAdapter = {
|
|
|
21994
23112
|
|
|
21995
23113
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21996
23114
|
import fs21 from "node:fs";
|
|
21997
|
-
import
|
|
23115
|
+
import path30 from "node:path";
|
|
21998
23116
|
var undocumented = { support: "undocumented", native: null };
|
|
21999
23117
|
var none = { support: "none", native: null };
|
|
22000
23118
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -22191,7 +23309,7 @@ var protocolEvent2 = (input) => {
|
|
|
22191
23309
|
};
|
|
22192
23310
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
22193
23311
|
var lastAssistantText = (file) => {
|
|
22194
|
-
if (!file || !
|
|
23312
|
+
if (!file || !path30.isAbsolute(file))
|
|
22195
23313
|
return null;
|
|
22196
23314
|
try {
|
|
22197
23315
|
const size = fs21.statSync(file).size;
|
|
@@ -22228,7 +23346,7 @@ var lastAssistantText = (file) => {
|
|
|
22228
23346
|
return null;
|
|
22229
23347
|
}
|
|
22230
23348
|
};
|
|
22231
|
-
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
23349
|
+
var within3 = (root, dir) => dir === root || dir.startsWith(root.endsWith(path30.sep) ? root : `${root}${path30.sep}`);
|
|
22232
23350
|
var hookCwd = (input) => {
|
|
22233
23351
|
const cwd = existingDirectory(input.cwd);
|
|
22234
23352
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|